Skip to content

Commit

Permalink
Merge pull request #67 from pluralsh/marcin/prod-2718-implement-plura…
Browse files Browse the repository at this point in the history
…l_oidc_provider-terraform-resource

feat: Add OIDC provider resource
  • Loading branch information
michaeljguarino authored Oct 11, 2024
2 parents a0b7900 + c984adc commit 85c5978
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 14 deletions.
32 changes: 32 additions & 0 deletions docs/resources/oidc_provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "plural_oidc_provider Resource - terraform-provider-plural"
subcategory: ""
description: |-
---

# plural_oidc_provider (Resource)





<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `name` (String) Human-readable name of this OIDC provider.
- `type` (String)

### Optional

- `description` (String) Description of this OIDC provider.
- `redirect_uris` (Set of String)

### Read-Only

- `client_id` (String, Sensitive)
- `client_secret` (String, Sensitive)
- `id` (String) Internal identifier of this OIDC provider.
20 changes: 20 additions & 0 deletions example/oidcprovider/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
terraform {
required_providers {
plural = {
source = "pluralsh/plural"
version = "0.2.1"
}
}
}

provider "plural" {
use_cli = true
}

resource "plural_oidc_provider" "provider" {
name = "tf-test-provider"
auth_method = "BASIC"
type = "PLURAL"
description = "test provider"
redirect_uris = ["localhost:8000"]
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require (
github.com/hashicorp/terraform-plugin-framework-validators v0.13.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/mitchellh/go-homedir v1.1.0
github.com/pluralsh/console/go/client v1.15.0
github.com/pluralsh/console/go/client v1.21.1
github.com/pluralsh/plural-cli v0.9.14-0.20240730152129-7ce540b144fd
github.com/pluralsh/polly v0.1.10
github.com/samber/lo v1.46.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -657,8 +657,8 @@ github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFz
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pluralsh/console/go/client v1.15.0 h1:dFbTGs6648o1VfaS5ZwBcXOKfQUtjIDCs9/8l13PsXU=
github.com/pluralsh/console/go/client v1.15.0/go.mod h1:lpoWASYsM9keNePS3dpFiEisUHEfObIVlSL3tzpKn8k=
github.com/pluralsh/console/go/client v1.21.1 h1:uYTc54qJbP/7tpsVG4pELasY/+05J+EO8yo7pMu9thw=
github.com/pluralsh/console/go/client v1.21.1/go.mod h1:lpoWASYsM9keNePS3dpFiEisUHEfObIVlSL3tzpKn8k=
github.com/pluralsh/gqlclient v1.12.1 h1:JDOkP9jjqkPdTYdpH5hooG4F8T6FDH90XfipeXJmJFY=
github.com/pluralsh/gqlclient v1.12.1/go.mod h1:OEjN9L63x8m3A3eQBv5kVkFgiY9fp2aZ0cgOF0uII58=
github.com/pluralsh/plural-cli v0.9.14-0.20240730152129-7ce540b144fd h1:gg+5AUbiip8WzAQE+avozXLBdP5eS19J6x6+BhtkGNY=
Expand Down
20 changes: 20 additions & 0 deletions internal/common/set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package common

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func SetFrom(values []*string, config types.Set, ctx context.Context, d diag.Diagnostics) types.Set {
if len(values) == 0 {
// Rewriting config to state to avoid inconsistent result errors.
// This could happen, for example, when sending "nil" to API and "[]" is returned as a result.
return config
}

setValue, diags := types.SetValueFrom(ctx, types.StringType, values)
d.Append(diags...)
return setValue
}
13 changes: 2 additions & 11 deletions internal/model/custom_stack_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

"terraform-provider-plural/internal/client"
"terraform-provider-plural/internal/common"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
Expand Down Expand Up @@ -116,7 +117,7 @@ func commandsFrom(commands []*gqlclient.StackCommandFragment, config types.Set,
for i, command := range commands {
objValue, diags := types.ObjectValueFrom(ctx, CustomStackRunCommandAttrTypes, CustomStackRunCommand{
Cmd: types.StringValue(command.Cmd),
Args: commandArgsFrom(command.Args, ctx, d),
Args: common.SetFrom(command.Args, types.SetNull(types.StringType), ctx, d),
Dir: types.StringPointerValue(command.Dir),
})
values[i] = objValue
Expand All @@ -128,16 +129,6 @@ func commandsFrom(commands []*gqlclient.StackCommandFragment, config types.Set,
return setValue
}

func commandArgsFrom(values []*string, ctx context.Context, d diag.Diagnostics) types.Set {
if values == nil {
return types.SetNull(types.StringType)
}

setValue, diags := types.SetValueFrom(ctx, types.StringType, values)
d.Append(diags...)
return setValue
}

type CustomStackRunConfiguration struct {
Type types.String `tfsdk:"type"`
Name types.String `tfsdk:"name"`
Expand Down
78 changes: 78 additions & 0 deletions internal/model/oidc_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package model

import (
"context"

"terraform-provider-plural/internal/common"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
gqlclient "github.com/pluralsh/console/go/client"
"github.com/pluralsh/polly/algorithms"
"github.com/samber/lo"
)

type OIDCProvider struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Type types.String `tfsdk:"type"`
Description types.String `tfsdk:"description"`
ClientID types.String `tfsdk:"client_id"`
ClientSecret types.String `tfsdk:"client_secret"`
AuthMethod types.String `tfsdk:"auth_method"`
RedirectURIs types.Set `tfsdk:"redirect_uris"`
}

func (p *OIDCProvider) Attributes(ctx context.Context, d diag.Diagnostics) gqlclient.OidcProviderAttributes {
return gqlclient.OidcProviderAttributes{
Name: p.Name.ValueString(),
Description: p.descriptionAttribute(),
AuthMethod: p.authMethodAttribute(),
RedirectUris: p.redirectURIsAttribute(ctx, d),
}
}

func (p *OIDCProvider) descriptionAttribute() *string {
if p.Description.IsNull() {
// Setting to empty string as using null will have no effect even if it was deleted from config.
return lo.ToPtr("")
}

return p.Description.ValueStringPointer()
}

func (p *OIDCProvider) authMethodAttribute() *gqlclient.OidcAuthMethod {
if p.AuthMethod.IsNull() {
return nil
}

return lo.ToPtr(gqlclient.OidcAuthMethod(p.AuthMethod.ValueString()))
}

func (p *OIDCProvider) redirectURIsAttribute(ctx context.Context, d diag.Diagnostics) []*string {
redirectURIs := make([]types.String, len(p.RedirectURIs.Elements()))
d.Append(p.RedirectURIs.ElementsAs(ctx, &redirectURIs, false)...)
return algorithms.Map(redirectURIs, func(v types.String) *string { return v.ValueStringPointer() })
}

func (p *OIDCProvider) TypeAttribute() gqlclient.OidcProviderType {
return gqlclient.OidcProviderType(p.Type.ValueString())
}

func (p *OIDCProvider) From(response *gqlclient.OIDCProviderFragment, ctx context.Context, d diag.Diagnostics) {
p.ID = types.StringValue(response.ID)
p.Name = types.StringValue(response.Name)
p.Description = types.StringPointerValue(response.Description)
p.ClientID = types.StringValue(response.ClientID)
p.ClientSecret = types.StringValue(response.ClientSecret)
p.AuthMethod = p.authMethodFrom(response.AuthMethod)
p.RedirectURIs = common.SetFrom(response.RedirectUris, p.RedirectURIs, ctx, d)
}

func (p *OIDCProvider) authMethodFrom(authMethod *gqlclient.OidcAuthMethod) types.String {
if authMethod == nil {
return types.StringNull()
}

return types.StringValue(string(*authMethod))
}
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ func (p *PluralProvider) Resources(_ context.Context) []func() resource.Resource
r.NewPrAutomationTriggerResource,
r.NewStackRunTriggerResource,
r.NewSharedSecretResource,
r.NewOIDCProviderResourceResource,
}
}

Expand Down
171 changes: 171 additions & 0 deletions internal/resource/oidc_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package resource

import (
"context"
"fmt"

"terraform-provider-plural/internal/client"
"terraform-provider-plural/internal/common"
"terraform-provider-plural/internal/model"

"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
gqlclient "github.com/pluralsh/console/go/client"
"github.com/pluralsh/polly/algorithms"
)

var _ resource.Resource = &OIDCProviderResource{}
var _ resource.ResourceWithImportState = &OIDCProviderResource{}

func NewOIDCProviderResourceResource() resource.Resource {
return &OIDCProviderResource{}
}

// OIDCProviderResource defines the OIDC provider resource implementation.
type OIDCProviderResource struct {
client *client.Client
}

func (r *OIDCProviderResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_oidc_provider"
}

func (r *OIDCProviderResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "Internal identifier of this OIDC provider.",
MarkdownDescription: "Internal identifier of this OIDC provider.",
Computed: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
},
"name": schema.StringAttribute{
Description: "Human-readable name of this OIDC provider.",
MarkdownDescription: "Human-readable name of this OIDC provider.",
Required: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
},
"type": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
Validators: []validator.String{
stringvalidator.OneOf(
algorithms.Map(gqlclient.AllOidcProviderType,
func(t gqlclient.OidcProviderType) string { return string(t) })...),
},
},
"description": schema.StringAttribute{
Description: "Description of this OIDC provider.",
MarkdownDescription: "Description of this OIDC provider.",
Optional: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"client_id": schema.StringAttribute{
Computed: true,
Sensitive: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
},
"client_secret": schema.StringAttribute{
Computed: true,
Sensitive: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
},
"auth_method": schema.StringAttribute{
Optional: true,
Computed: true,
Default: stringdefault.StaticString(string(gqlclient.OidcAuthMethodBasic)),
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
Validators: []validator.String{
stringvalidator.OneOf(
algorithms.Map(gqlclient.AllOidcAuthMethod,
func(t gqlclient.OidcAuthMethod) string { return string(t) })...),
},
},
"redirect_uris": schema.SetAttribute{
Optional: true,
ElementType: types.StringType,
},
},
}
}

func (r *OIDCProviderResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

data, ok := req.ProviderData.(*common.ProviderData)
if !ok {
resp.Diagnostics.AddError(
"Unexpected OIDC Provider Resource Configure Type",
fmt.Sprintf("Expected *common.ProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

r.client = data.Client
}

func (r *OIDCProviderResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
data := new(model.OIDCProvider)
resp.Diagnostics.Append(req.Plan.Get(ctx, data)...)
if resp.Diagnostics.HasError() {
return
}

result, err := r.client.CreateOIDCProvider(ctx, data.TypeAttribute(), data.Attributes(ctx, resp.Diagnostics))
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create OIDC provider, got error: %s", err))
return
}

data.From(result.CreateOidcProvider, ctx, resp.Diagnostics)
resp.Diagnostics.Append(resp.State.Set(ctx, data)...)
}

func (r *OIDCProviderResource) Read(_ context.Context, _ resource.ReadRequest, _ *resource.ReadResponse) {
// Ignore.
}

func (r *OIDCProviderResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
data := new(model.OIDCProvider)
resp.Diagnostics.Append(req.Plan.Get(ctx, data)...)
if resp.Diagnostics.HasError() {
return
}

result, err := r.client.UpdateOIDCProvider(ctx, data.ID.ValueString(), data.TypeAttribute(), data.Attributes(ctx, resp.Diagnostics))
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update OIDC provider, got error: %s", err))
return
}

data.From(result.UpdateOidcProvider, ctx, resp.Diagnostics)
resp.Diagnostics.Append(resp.State.Set(ctx, data)...)
}

func (r *OIDCProviderResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
data := new(model.OIDCProvider)
resp.Diagnostics.Append(req.State.Get(ctx, data)...)
if resp.Diagnostics.HasError() {
return
}

_, err := r.client.DeleteOIDCProvider(ctx, data.ID.ValueString(), data.TypeAttribute())
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete OIDC provider, got error: %s", err))
return
}
}

func (r *OIDCProviderResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}

0 comments on commit 85c5978

Please sign in to comment.