Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GroupOwner resource #2079

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions examples/resources/okta_group_owner/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# okta_group_owner

This resource represents a Group Owner for an Okta organization. More information can
be found in the
[Group Owners](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/GroupOwner/#tag/GroupOwner) API
documentation.

- Example [resource.tf](./resource.tf)
16 changes: 16 additions & 0 deletions examples/resources/okta_group_owner/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
resource "okta_user" "test" {
first_name = "TestAcc"
last_name = "Smith"
login = "[email protected]"
email = "[email protected]"
}

resource "okta_group" "test" {
name = "testAcc_replace_with_uuid"
}

resource "okta_group_owner" "test" {
group_id = okta_group.test.id
id_of_group_owner = okta_user.test.id
type = "USER"
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ require (
github.com/kelseyhightower/envconfig v1.4.0
github.com/lestrrat-go/jwx v1.2.29
github.com/okta/okta-sdk-golang/v4 v4.1.2
github.com/okta/okta-sdk-golang/v5 v5.0.0
github.com/okta/okta-sdk-golang/v5 v5.0.2
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/stretchr/testify v1.9.0
gopkg.in/dnaeon/go-vcr.v3 v3.1.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ github.com/okta/okta-sdk-golang/v4 v4.1.2 h1:gSycAYWGrvYeXBW8HakMZnNu/ptMuTvTQ/z
github.com/okta/okta-sdk-golang/v4 v4.1.2/go.mod h1:01oiHDXvZQHlZo1Uw084VDYwXIqJe19z34b53PBZpUY=
github.com/okta/okta-sdk-golang/v5 v5.0.0 h1:QYe3pPSg1eI3SSKzBBXDUPdxmAzFzQ5Ukp0hSlGifGM=
github.com/okta/okta-sdk-golang/v5 v5.0.0/go.mod h1:T/vmECtJX33YPZSVD+sorebd8LLhe38Bi/VrFTjgVX0=
github.com/okta/okta-sdk-golang/v5 v5.0.2 h1:eecvycE/XDX56IWTsOVhqfj5txCgqryTXzKy7wKEq78=
github.com/okta/okta-sdk-golang/v5 v5.0.2/go.mod h1:T/vmECtJX33YPZSVD+sorebd8LLhe38Bi/VrFTjgVX0=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
Expand Down
1 change: 1 addition & 0 deletions okta/framework_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ func (p *FrameworkProvider) Resources(_ context.Context) []func() resource.Resou
NewPolicyDeviceAssuranceWindowsResource,
NewCustomizedSigninResource,
NewPreviewSigninResource,
GroupOwnerResource,
}
}

Expand Down
1 change: 1 addition & 0 deletions okta/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ const (
factor = "okta_factor"
factorTotp = "okta_factor_totp"
group = "okta_group"
groupOwner = "okta_group_owner"
groupEveryone = "okta_everyone_group"
groupMemberships = "okta_group_memberships"
groupRole = "okta_group_role"
Expand Down
236 changes: 236 additions & 0 deletions okta/resource_okta_group_owner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package okta

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/diag"
"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/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/okta/okta-sdk-golang/v5/okta"
)

func GroupOwnerResource() resource.Resource {
return &groupOwnerResource{}
}

type groupOwnerResource struct {
*Config
}

type groupOwnerResourceModel struct {
DisplayName types.String `tfsdk:"display_name"`
GroupID types.String `tfsdk:"group_id"`
IdOfGroupOwner types.String `tfsdk:"id_of_group_owner"`
ID types.String `tfsdk:"id"`
OriginId types.String `tfsdk:"origin_id"`
OriginType types.String `tfsdk:"origin_type"`
Resolved types.Bool `tfsdk:"resolved"`
Type types.String `tfsdk:"type"`
}

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

func (r *groupOwnerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: `Manages group owner resource.`,
Attributes: map[string]schema.Attribute{
"display_name": schema.StringAttribute{
Description: "The display name of the group owner",
Computed: true,
},
"group_id": schema.StringAttribute{
Description: "The id of the group",
Required: true,
},
"id_of_group_owner": schema.StringAttribute{
Description: "The user id of the group owner",
Required: true,
},
"id": schema.StringAttribute{
Description: "The id of the group owner resource",
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"origin_id": schema.StringAttribute{
Description: "The ID of the app instance if the originType is APPLICATION. This value is NULL if originType is OKTA_DIRECTORY.",
Computed: true,
},
"origin_type": schema.StringAttribute{
Description: "The source where group ownership is managed. Enum: \"APPLICATION\" \"OKTA_DIRECTORY\"",
Computed: true,
},
"resolved": schema.BoolAttribute{
Description: "If originType is APPLICATION, this parameter is set to FALSE until the owner's originId is reconciled with an associated Okta ID.",
Computed: true,
},
"type": schema.StringAttribute{
Description: "The entity type of the owner. Enum: \"GROUP\" \"USER\"",
Required: true,
},
},
}
}

// Configure adds the provider configured client to the resource.
func (r *groupOwnerResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
r.Config = resourceConfiguration(req, resp)
}

func (r *groupOwnerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var state groupOwnerResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

createReqBody, err := buildCreateGroupOwnerRequest(state)
if err != nil {
resp.Diagnostics.AddError(
"failed to build group owner request",
err.Error(),
)
return
}

createdGroupOwner, _, err := r.oktaSDKClientV5.GroupOwnerAPI.AssignGroupOwner(ctx, state.GroupID.ValueString()).AssignGroupOwnerRequestBody(createReqBody).Execute()
if err != nil {
resp.Diagnostics.AddError(
"failed to create group owner for group "+state.GroupID.ValueString()+" for group owner user id: "+createReqBody.GetId()+", type: "+createReqBody.GetType(),
err.Error(),
)
return
}

resp.Diagnostics.Append(mapGroupOwnerToState(createdGroupOwner, &state)...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
}

func (r *groupOwnerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state groupOwnerResourceModel
resp.Diagnostics.Append(resp.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

var grpOwner *okta.GroupOwner
var err error

listGroupOwners, _, err := r.Config.oktaSDKClientV5.GroupOwnerAPI.ListGroupOwners(ctx, state.GroupID.ValueString()).Execute()

if err != nil {
resp.Diagnostics.AddError(
"Error retrieving list group owners",
fmt.Sprintf("Error returned: %s", err.Error()),
)
return
}

for _, groupOwner := range listGroupOwners {
if groupOwner.GetId() == state.ID.ValueString() {
grpOwner = &groupOwner
}
}

resp.Diagnostics.Append(mapGroupOwnerToState(grpOwner, &state)...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
}

func (r *groupOwnerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state groupOwnerResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

_, err := r.oktaSDKClientV5.GroupOwnerAPI.DeleteGroupOwner(ctx, state.GroupID.ValueString(), state.ID.ValueString()).Execute()
if err != nil {
resp.Diagnostics.AddError(
"failed to delete group owner "+state.ID.ValueString()+" from group",
err.Error(),
)
return
}
}

func (r *groupOwnerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var state groupOwnerResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

createReqBody, err := buildCreateGroupOwnerRequest(state)
if err != nil {
resp.Diagnostics.AddError(
"failed to build group owner request",
err.Error(),
)
return
}

createdGroupOwner, _, err := r.oktaSDKClientV5.GroupOwnerAPI.AssignGroupOwner(ctx, state.GroupID.ValueString()).AssignGroupOwnerRequestBody(createReqBody).Execute()
if err != nil {
resp.Diagnostics.AddError(
"failed to update/assign group owner",
err.Error(),
)
return
}

resp.Diagnostics.Append(mapGroupOwnerToState(createdGroupOwner, &state)...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
}

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

func buildCreateGroupOwnerRequest(model groupOwnerResourceModel) (okta.AssignGroupOwnerRequestBody, error) {
return okta.AssignGroupOwnerRequestBody{
Id: model.IdOfGroupOwner.ValueStringPointer(),
Type: model.Type.ValueStringPointer(),
}, nil
}

func mapGroupOwnerToState(data *okta.GroupOwner, state *groupOwnerResourceModel) diag.Diagnostics {
var diags diag.Diagnostics

state.ID = types.StringPointerValue(data.Id)
state.DisplayName = types.StringPointerValue(data.DisplayName)
state.OriginId = types.StringPointerValue(data.OriginId)
state.OriginType = types.StringPointerValue(data.OriginType)
state.Resolved = types.BoolPointerValue(data.Resolved)
state.Type = types.StringPointerValue(data.Type)

return diags
}
29 changes: 29 additions & 0 deletions okta/resource_okta_group_owner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package okta

import (
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccResourceOktaGroupOwner_crud(t *testing.T) {
mgr := newFixtureManager("resources", "okta_group_owner", t.Name())
config := mgr.GetFixtures("resource.tf", t)

oktaResourceTest(
t, resource.TestCase{
PreCheck: testAccPreCheck(t),
ErrorCheck: testAccErrorChecks(t),
ProtoV5ProviderFactories: testAccMergeProvidersFactories,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("okta_user.test", "first_name", "TestAcc"),
resource.TestCheckResourceAttr("okta_user.test", "last_name", "Smith"),
resource.TestCheckResourceAttr("okta_group_owner.test", "type", "USER"),
),
},
},
})
}