Skip to content

Commit 85c5978

Browse files
Merge pull request #67 from pluralsh/marcin/prod-2718-implement-plural_oidc_provider-terraform-resource
feat: Add OIDC provider resource
2 parents a0b7900 + c984adc commit 85c5978

File tree

9 files changed

+327
-14
lines changed

9 files changed

+327
-14
lines changed

docs/resources/oidc_provider.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "plural_oidc_provider Resource - terraform-provider-plural"
4+
subcategory: ""
5+
description: |-
6+
7+
---
8+
9+
# plural_oidc_provider (Resource)
10+
11+
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Required
19+
20+
- `name` (String) Human-readable name of this OIDC provider.
21+
- `type` (String)
22+
23+
### Optional
24+
25+
- `description` (String) Description of this OIDC provider.
26+
- `redirect_uris` (Set of String)
27+
28+
### Read-Only
29+
30+
- `client_id` (String, Sensitive)
31+
- `client_secret` (String, Sensitive)
32+
- `id` (String) Internal identifier of this OIDC provider.

example/oidcprovider/main.tf

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
terraform {
2+
required_providers {
3+
plural = {
4+
source = "pluralsh/plural"
5+
version = "0.2.1"
6+
}
7+
}
8+
}
9+
10+
provider "plural" {
11+
use_cli = true
12+
}
13+
14+
resource "plural_oidc_provider" "provider" {
15+
name = "tf-test-provider"
16+
auth_method = "BASIC"
17+
type = "PLURAL"
18+
description = "test provider"
19+
redirect_uris = ["localhost:8000"]
20+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ require (
1212
github.com/hashicorp/terraform-plugin-framework-validators v0.13.0
1313
github.com/hashicorp/terraform-plugin-log v0.9.0
1414
github.com/mitchellh/go-homedir v1.1.0
15-
github.com/pluralsh/console/go/client v1.15.0
15+
github.com/pluralsh/console/go/client v1.21.1
1616
github.com/pluralsh/plural-cli v0.9.14-0.20240730152129-7ce540b144fd
1717
github.com/pluralsh/polly v0.1.10
1818
github.com/samber/lo v1.46.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,8 +657,8 @@ github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFz
657657
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
658658
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
659659
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
660-
github.com/pluralsh/console/go/client v1.15.0 h1:dFbTGs6648o1VfaS5ZwBcXOKfQUtjIDCs9/8l13PsXU=
661-
github.com/pluralsh/console/go/client v1.15.0/go.mod h1:lpoWASYsM9keNePS3dpFiEisUHEfObIVlSL3tzpKn8k=
660+
github.com/pluralsh/console/go/client v1.21.1 h1:uYTc54qJbP/7tpsVG4pELasY/+05J+EO8yo7pMu9thw=
661+
github.com/pluralsh/console/go/client v1.21.1/go.mod h1:lpoWASYsM9keNePS3dpFiEisUHEfObIVlSL3tzpKn8k=
662662
github.com/pluralsh/gqlclient v1.12.1 h1:JDOkP9jjqkPdTYdpH5hooG4F8T6FDH90XfipeXJmJFY=
663663
github.com/pluralsh/gqlclient v1.12.1/go.mod h1:OEjN9L63x8m3A3eQBv5kVkFgiY9fp2aZ0cgOF0uII58=
664664
github.com/pluralsh/plural-cli v0.9.14-0.20240730152129-7ce540b144fd h1:gg+5AUbiip8WzAQE+avozXLBdP5eS19J6x6+BhtkGNY=

internal/common/set.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package common
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/diag"
7+
"github.com/hashicorp/terraform-plugin-framework/types"
8+
)
9+
10+
func SetFrom(values []*string, config types.Set, ctx context.Context, d diag.Diagnostics) types.Set {
11+
if len(values) == 0 {
12+
// Rewriting config to state to avoid inconsistent result errors.
13+
// This could happen, for example, when sending "nil" to API and "[]" is returned as a result.
14+
return config
15+
}
16+
17+
setValue, diags := types.SetValueFrom(ctx, types.StringType, values)
18+
d.Append(diags...)
19+
return setValue
20+
}

internal/model/custom_stack_run.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55

66
"terraform-provider-plural/internal/client"
7+
"terraform-provider-plural/internal/common"
78

89
"github.com/hashicorp/terraform-plugin-framework/attr"
910
"github.com/hashicorp/terraform-plugin-framework/diag"
@@ -116,7 +117,7 @@ func commandsFrom(commands []*gqlclient.StackCommandFragment, config types.Set,
116117
for i, command := range commands {
117118
objValue, diags := types.ObjectValueFrom(ctx, CustomStackRunCommandAttrTypes, CustomStackRunCommand{
118119
Cmd: types.StringValue(command.Cmd),
119-
Args: commandArgsFrom(command.Args, ctx, d),
120+
Args: common.SetFrom(command.Args, types.SetNull(types.StringType), ctx, d),
120121
Dir: types.StringPointerValue(command.Dir),
121122
})
122123
values[i] = objValue
@@ -128,16 +129,6 @@ func commandsFrom(commands []*gqlclient.StackCommandFragment, config types.Set,
128129
return setValue
129130
}
130131

131-
func commandArgsFrom(values []*string, ctx context.Context, d diag.Diagnostics) types.Set {
132-
if values == nil {
133-
return types.SetNull(types.StringType)
134-
}
135-
136-
setValue, diags := types.SetValueFrom(ctx, types.StringType, values)
137-
d.Append(diags...)
138-
return setValue
139-
}
140-
141132
type CustomStackRunConfiguration struct {
142133
Type types.String `tfsdk:"type"`
143134
Name types.String `tfsdk:"name"`

internal/model/oidc_provider.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package model
2+
3+
import (
4+
"context"
5+
6+
"terraform-provider-plural/internal/common"
7+
8+
"github.com/hashicorp/terraform-plugin-framework/diag"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
gqlclient "github.com/pluralsh/console/go/client"
11+
"github.com/pluralsh/polly/algorithms"
12+
"github.com/samber/lo"
13+
)
14+
15+
type OIDCProvider struct {
16+
ID types.String `tfsdk:"id"`
17+
Name types.String `tfsdk:"name"`
18+
Type types.String `tfsdk:"type"`
19+
Description types.String `tfsdk:"description"`
20+
ClientID types.String `tfsdk:"client_id"`
21+
ClientSecret types.String `tfsdk:"client_secret"`
22+
AuthMethod types.String `tfsdk:"auth_method"`
23+
RedirectURIs types.Set `tfsdk:"redirect_uris"`
24+
}
25+
26+
func (p *OIDCProvider) Attributes(ctx context.Context, d diag.Diagnostics) gqlclient.OidcProviderAttributes {
27+
return gqlclient.OidcProviderAttributes{
28+
Name: p.Name.ValueString(),
29+
Description: p.descriptionAttribute(),
30+
AuthMethod: p.authMethodAttribute(),
31+
RedirectUris: p.redirectURIsAttribute(ctx, d),
32+
}
33+
}
34+
35+
func (p *OIDCProvider) descriptionAttribute() *string {
36+
if p.Description.IsNull() {
37+
// Setting to empty string as using null will have no effect even if it was deleted from config.
38+
return lo.ToPtr("")
39+
}
40+
41+
return p.Description.ValueStringPointer()
42+
}
43+
44+
func (p *OIDCProvider) authMethodAttribute() *gqlclient.OidcAuthMethod {
45+
if p.AuthMethod.IsNull() {
46+
return nil
47+
}
48+
49+
return lo.ToPtr(gqlclient.OidcAuthMethod(p.AuthMethod.ValueString()))
50+
}
51+
52+
func (p *OIDCProvider) redirectURIsAttribute(ctx context.Context, d diag.Diagnostics) []*string {
53+
redirectURIs := make([]types.String, len(p.RedirectURIs.Elements()))
54+
d.Append(p.RedirectURIs.ElementsAs(ctx, &redirectURIs, false)...)
55+
return algorithms.Map(redirectURIs, func(v types.String) *string { return v.ValueStringPointer() })
56+
}
57+
58+
func (p *OIDCProvider) TypeAttribute() gqlclient.OidcProviderType {
59+
return gqlclient.OidcProviderType(p.Type.ValueString())
60+
}
61+
62+
func (p *OIDCProvider) From(response *gqlclient.OIDCProviderFragment, ctx context.Context, d diag.Diagnostics) {
63+
p.ID = types.StringValue(response.ID)
64+
p.Name = types.StringValue(response.Name)
65+
p.Description = types.StringPointerValue(response.Description)
66+
p.ClientID = types.StringValue(response.ClientID)
67+
p.ClientSecret = types.StringValue(response.ClientSecret)
68+
p.AuthMethod = p.authMethodFrom(response.AuthMethod)
69+
p.RedirectURIs = common.SetFrom(response.RedirectUris, p.RedirectURIs, ctx, d)
70+
}
71+
72+
func (p *OIDCProvider) authMethodFrom(authMethod *gqlclient.OidcAuthMethod) types.String {
73+
if authMethod == nil {
74+
return types.StringNull()
75+
}
76+
77+
return types.StringValue(string(*authMethod))
78+
}

internal/provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ func (p *PluralProvider) Resources(_ context.Context) []func() resource.Resource
194194
r.NewPrAutomationTriggerResource,
195195
r.NewStackRunTriggerResource,
196196
r.NewSharedSecretResource,
197+
r.NewOIDCProviderResourceResource,
197198
}
198199
}
199200

internal/resource/oidc_provider.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package resource
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"terraform-provider-plural/internal/client"
8+
"terraform-provider-plural/internal/common"
9+
"terraform-provider-plural/internal/model"
10+
11+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
12+
"github.com/hashicorp/terraform-plugin-framework/path"
13+
"github.com/hashicorp/terraform-plugin-framework/resource"
14+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
15+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
16+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
17+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
18+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
19+
"github.com/hashicorp/terraform-plugin-framework/types"
20+
gqlclient "github.com/pluralsh/console/go/client"
21+
"github.com/pluralsh/polly/algorithms"
22+
)
23+
24+
var _ resource.Resource = &OIDCProviderResource{}
25+
var _ resource.ResourceWithImportState = &OIDCProviderResource{}
26+
27+
func NewOIDCProviderResourceResource() resource.Resource {
28+
return &OIDCProviderResource{}
29+
}
30+
31+
// OIDCProviderResource defines the OIDC provider resource implementation.
32+
type OIDCProviderResource struct {
33+
client *client.Client
34+
}
35+
36+
func (r *OIDCProviderResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
37+
resp.TypeName = req.ProviderTypeName + "_oidc_provider"
38+
}
39+
40+
func (r *OIDCProviderResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
41+
resp.Schema = schema.Schema{
42+
Attributes: map[string]schema.Attribute{
43+
"id": schema.StringAttribute{
44+
Description: "Internal identifier of this OIDC provider.",
45+
MarkdownDescription: "Internal identifier of this OIDC provider.",
46+
Computed: true,
47+
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
48+
},
49+
"name": schema.StringAttribute{
50+
Description: "Human-readable name of this OIDC provider.",
51+
MarkdownDescription: "Human-readable name of this OIDC provider.",
52+
Required: true,
53+
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
54+
},
55+
"type": schema.StringAttribute{
56+
Required: true,
57+
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
58+
Validators: []validator.String{
59+
stringvalidator.OneOf(
60+
algorithms.Map(gqlclient.AllOidcProviderType,
61+
func(t gqlclient.OidcProviderType) string { return string(t) })...),
62+
},
63+
},
64+
"description": schema.StringAttribute{
65+
Description: "Description of this OIDC provider.",
66+
MarkdownDescription: "Description of this OIDC provider.",
67+
Optional: true,
68+
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
69+
},
70+
"client_id": schema.StringAttribute{
71+
Computed: true,
72+
Sensitive: true,
73+
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
74+
},
75+
"client_secret": schema.StringAttribute{
76+
Computed: true,
77+
Sensitive: true,
78+
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
79+
},
80+
"auth_method": schema.StringAttribute{
81+
Optional: true,
82+
Computed: true,
83+
Default: stringdefault.StaticString(string(gqlclient.OidcAuthMethodBasic)),
84+
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
85+
Validators: []validator.String{
86+
stringvalidator.OneOf(
87+
algorithms.Map(gqlclient.AllOidcAuthMethod,
88+
func(t gqlclient.OidcAuthMethod) string { return string(t) })...),
89+
},
90+
},
91+
"redirect_uris": schema.SetAttribute{
92+
Optional: true,
93+
ElementType: types.StringType,
94+
},
95+
},
96+
}
97+
}
98+
99+
func (r *OIDCProviderResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
100+
if req.ProviderData == nil {
101+
return
102+
}
103+
104+
data, ok := req.ProviderData.(*common.ProviderData)
105+
if !ok {
106+
resp.Diagnostics.AddError(
107+
"Unexpected OIDC Provider Resource Configure Type",
108+
fmt.Sprintf("Expected *common.ProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
109+
)
110+
111+
return
112+
}
113+
114+
r.client = data.Client
115+
}
116+
117+
func (r *OIDCProviderResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
118+
data := new(model.OIDCProvider)
119+
resp.Diagnostics.Append(req.Plan.Get(ctx, data)...)
120+
if resp.Diagnostics.HasError() {
121+
return
122+
}
123+
124+
result, err := r.client.CreateOIDCProvider(ctx, data.TypeAttribute(), data.Attributes(ctx, resp.Diagnostics))
125+
if err != nil {
126+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create OIDC provider, got error: %s", err))
127+
return
128+
}
129+
130+
data.From(result.CreateOidcProvider, ctx, resp.Diagnostics)
131+
resp.Diagnostics.Append(resp.State.Set(ctx, data)...)
132+
}
133+
134+
func (r *OIDCProviderResource) Read(_ context.Context, _ resource.ReadRequest, _ *resource.ReadResponse) {
135+
// Ignore.
136+
}
137+
138+
func (r *OIDCProviderResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
139+
data := new(model.OIDCProvider)
140+
resp.Diagnostics.Append(req.Plan.Get(ctx, data)...)
141+
if resp.Diagnostics.HasError() {
142+
return
143+
}
144+
145+
result, err := r.client.UpdateOIDCProvider(ctx, data.ID.ValueString(), data.TypeAttribute(), data.Attributes(ctx, resp.Diagnostics))
146+
if err != nil {
147+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update OIDC provider, got error: %s", err))
148+
return
149+
}
150+
151+
data.From(result.UpdateOidcProvider, ctx, resp.Diagnostics)
152+
resp.Diagnostics.Append(resp.State.Set(ctx, data)...)
153+
}
154+
155+
func (r *OIDCProviderResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
156+
data := new(model.OIDCProvider)
157+
resp.Diagnostics.Append(req.State.Get(ctx, data)...)
158+
if resp.Diagnostics.HasError() {
159+
return
160+
}
161+
162+
_, err := r.client.DeleteOIDCProvider(ctx, data.ID.ValueString(), data.TypeAttribute())
163+
if err != nil {
164+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete OIDC provider, got error: %s", err))
165+
return
166+
}
167+
}
168+
169+
func (r *OIDCProviderResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
170+
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
171+
}

0 commit comments

Comments
 (0)