Skip to content

Commit

Permalink
feat: service account OIDC Identity resource (#821)
Browse files Browse the repository at this point in the history
  • Loading branch information
domenicsim1 authored Nov 21, 2024
1 parent e688aa7 commit 39fe8de
Show file tree
Hide file tree
Showing 9 changed files with 418 additions and 3 deletions.
32 changes: 32 additions & 0 deletions docs/data-sources/service_account_oidc_identity.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: "octopusdeploy_service_account_oidc_identity Data Source - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
---

# octopusdeploy_service_account_oidc_identity (Data Source)





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

### Required

- `service_account_id` (String) ID of the user associated to this identity

### Optional

- `id` (String) The unique ID for this resource.

### Read-Only

- `issuer` (String) OIDC issuer url
- `name` (String) Name of the user associated to this identity
- `subject` (String) OIDC subject claims


29 changes: 29 additions & 0 deletions docs/resources/service_account_oidc_identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "octopusdeploy_service_account_oidc_identity Resource - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
This resource manages manages OIDC service account for the associated user
---

# octopusdeploy_service_account_oidc_identity (Resource)

This resource manages manages OIDC service account for the associated user



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

### Required

- `issuer` (String) OIDC issuer url
- `name` (String) The name of this resource.
- `service_account_id` (String) ID of the user to associate this identity to
- `subject` (String) OIDC subject claims

### Read-Only

- `id` (String) The unique ID for this resource.


2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/OctopusDeploy/terraform-provider-octopusdeploy
go 1.21

require (
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.55.0
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.60.0
github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240729041805-46db6fb717b4
github.com/google/uuid v1.6.0
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ github.com/Microsoft/hcsshim v0.12.4 h1:Ev7YUMHAHoWNm+aDSPzc5W9s6E2jyL1szpVDJeZ/
github.com/Microsoft/hcsshim v0.12.4/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ=
github.com/OctopusDeploy/go-octodiff v1.0.0 h1:U+ORg6azniwwYo+O44giOw6TiD5USk8S4VDhOQ0Ven0=
github.com/OctopusDeploy/go-octodiff v1.0.0/go.mod h1:Mze0+EkOWTgTmi8++fyUc6r0aLZT7qD9gX+31t8MmIU=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.55.0 h1:kX6qRRy8AgbqTiYdenqVNe69pGhntwJGEgJx9rtn9/8=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.55.0/go.mod h1:ggvOXzMnq+w0pLg6C9zdjz6YBaHfO3B3tqmmB7JQdaw=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.60.0 h1:9j4IQ1UcAuaTytlBzQ7Mmoy/dLtofYfSGNiM22+sLXs=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.60.0/go.mod h1:ggvOXzMnq+w0pLg6C9zdjz6YBaHfO3B3tqmmB7JQdaw=
github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240729041805-46db6fb717b4 h1:QfbVf0bOIRMp/WHAWsuVDB7KHoWnRsGbvDuOf2ua7k4=
github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240729041805-46db6fb717b4/go.mod h1:Oq9KbiRNDBB5jFmrwnrgLX0urIqR/1ptY18TzkqXm7M=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg=
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package octopusdeploy_framework

import (
"context"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/serviceaccounts"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/types"
)

type serviceAccountOIDCIdentityDataSource struct {
*Config
}

func NewServiceAccountOIDCIdentityDataSource() datasource.DataSource {
return &serviceAccountOIDCIdentityDataSource{}
}

func (*serviceAccountOIDCIdentityDataSource) Metadata(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = util.GetTypeName(schemas.ServiceAccountOIDCIdentityDatasourceName)
}

func (s *serviceAccountOIDCIdentityDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
s.Config = DataSourceConfiguration(req, resp)
}

func (*serviceAccountOIDCIdentityDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schemas.ServiceAccountOIDCIdentitySchema{}.GetDatasourceSchema()
}

func (s *serviceAccountOIDCIdentityDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var err error
var data schemas.OIDCServiceAccountDatasourceSchemaModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

oidcIdentity, err := serviceaccounts.GetOIDCIdentityByID(s.Client, data.ServiceAccountID.ValueString(), data.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError("unable to load service account OIDC Identity", err.Error())
return
}

updateServiceAccountOIDCDataModel(oidcIdentity, &data)

resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func updateServiceAccountOIDCDataModel(request *serviceaccounts.OIDCIdentity, model *schemas.OIDCServiceAccountDatasourceSchemaModel) {
model.Name = types.StringValue(request.Name)
model.Issuer = types.StringValue(request.Issuer)
model.Subject = types.StringValue(request.Subject)
model.ID = types.StringValue(request.ID)
model.ServiceAccountID = types.StringValue(request.ServiceAccountID)
}
2 changes: 2 additions & 0 deletions octopusdeploy_framework/framework_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func (p *octopusDeployFrameworkProvider) DataSources(ctx context.Context) []func
NewScriptModuleDataSource,
NewTenantProjectDataSource,
NewUsersDataSource,
NewServiceAccountOIDCIdentityDataSource,
NewWorkersDataSource,
}
}
Expand Down Expand Up @@ -124,6 +125,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func()
NewSSHConnectionWorkerResource,
NewScriptModuleResource,
NewUserResource,
NewServiceAccountOIDCIdentity,
}
}

Expand Down
127 changes: 127 additions & 0 deletions octopusdeploy_framework/resource_service_account_oidc_identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package octopusdeploy_framework

import (
"context"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/serviceaccounts"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
)

var _ resource.Resource = &ServiceAccountOIDCIdentity{}

type ServiceAccountOIDCIdentity struct {
*Config
}

func NewServiceAccountOIDCIdentity() resource.Resource {
return &ServiceAccountOIDCIdentity{}
}

func (s *ServiceAccountOIDCIdentity) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = util.GetTypeName(schemas.ServiceAccountOIDCIdentityResourceName)
}

func (s *ServiceAccountOIDCIdentity) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schemas.ServiceAccountOIDCIdentitySchema{}.GetResourceSchema()
}

func (s *ServiceAccountOIDCIdentity) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
s.Config = ResourceConfiguration(req, resp)
}
func (s *ServiceAccountOIDCIdentity) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan schemas.OIDCServiceAccountSchemaModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
identityRequest := mapServiceAccountOIDCModelToRequest(&plan)
identityCreateResponse, err := serviceaccounts.AddOIDCIdentity(s.Client, identityRequest)
if err != nil {
resp.Diagnostics.AddError("Error creating OIDC identity", err.Error())
return
}
identityResponse, err := serviceaccounts.GetOIDCIdentityByID(s.Client, identityRequest.ServiceAccountID, identityCreateResponse.ID)
if err != nil {
resp.Diagnostics.AddError("Error creating OIDC identity", err.Error())
return
}

updateServiceAccountOIDCModel(identityResponse, &plan)
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

func (s *ServiceAccountOIDCIdentity) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state schemas.OIDCServiceAccountSchemaModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

identityResponse, err := serviceaccounts.GetOIDCIdentityByID(s.Client, state.ServiceAccountID.ValueString(), state.ID.ValueString())
if err != nil {
if err := errors.ProcessApiErrorV2(ctx, resp, state, err, "service account OIDC identity"); err != nil {
resp.Diagnostics.AddError("Error reading service account OIDC identity", err.Error())
}
return
}

updateServiceAccountOIDCModel(identityResponse, &state)
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
}

func (s *ServiceAccountOIDCIdentity) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan schemas.OIDCServiceAccountSchemaModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

identityRequest := mapServiceAccountOIDCModelToRequest(&plan)

err := serviceaccounts.UpdateOIDCIdentity(s.Client, identityRequest)
if err != nil {
resp.Diagnostics.AddError("Error updating service account OIDC identity", err.Error())
return
}
identityResponse, err := serviceaccounts.GetOIDCIdentityByID(s.Client, identityRequest.ServiceAccountID, identityRequest.ID)
if err != nil {
resp.Diagnostics.AddError("Error creating OIDC identity", err.Error())
return
}

updateServiceAccountOIDCModel(identityResponse, &plan)
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
}

func (s *ServiceAccountOIDCIdentity) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state schemas.OIDCServiceAccountSchemaModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

err := serviceaccounts.DeleteOIDCIdentityByID(s.Client, state.ServiceAccountID.ValueString(), state.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError("Error deleting service account OIDC identity", err.Error())
return
}
}

func mapServiceAccountOIDCModelToRequest(model *schemas.OIDCServiceAccountSchemaModel) *serviceaccounts.OIDCIdentity {
identity := serviceaccounts.NewOIDCIdentity(model.ServiceAccountID.ValueString(), model.Name.ValueString(), model.Issuer.ValueString(), model.Subject.ValueString())
identity.ID = model.ID.ValueString()
identity.Name = model.Name.ValueString()

return identity
}

func updateServiceAccountOIDCModel(request *serviceaccounts.OIDCIdentity, model *schemas.OIDCServiceAccountSchemaModel) {
model.Name = types.StringValue(request.Name)
model.Issuer = types.StringValue(request.Issuer)
model.Subject = types.StringValue(request.Subject)
model.ID = types.StringValue(request.ID)
model.ServiceAccountID = types.StringValue(request.ServiceAccountID)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package octopusdeploy_framework

import (
"fmt"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/serviceaccounts"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/users"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"testing"
)

func TestAccOctopusDeployServiceAccountOIDCIdentity(t *testing.T) {
localName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
prefix := "octopusdeploy_service_account_oidc_identity." + localName

localUserName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
userPrefix := " octopusdeploy_user." + localUserName

userData := users.User{
DisplayName: acctest.RandStringFromCharSet(20, acctest.CharSetAlpha),
EmailAddress: acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + "@test.com",
Username: acctest.RandStringFromCharSet(20, acctest.CharSetAlpha),
}

data := serviceaccounts.OIDCIdentity{
Name: acctest.RandStringFromCharSet(20, acctest.CharSetAlpha),
ServiceAccountID: userPrefix + ".id",
Issuer: "https://token.actions.githubusercontent.com",
Subject: "repo:test/test:environment:test",
}

resource.Test(t, resource.TestCase{
PreCheck: func() { TestAccPreCheck(t) },
ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
Steps: []resource.TestStep{
{
Config: testServiceAccountIdentityConfig(localName, localUserName, data, userData),
Check: resource.ComposeTestCheckFunc(
testScriptModuleExists(prefix),
resource.TestCheckResourceAttr(prefix, "name", data.Name),
resource.TestCheckResourceAttr(prefix, "issuer", data.Issuer),
resource.TestCheckResourceAttr(prefix, "subject", data.Subject),
),
},
{
Config: testServiceAccountIdentityUpdate(localName, localUserName, data, userData),
Check: resource.ComposeTestCheckFunc(
testScriptModuleExists(prefix),
resource.TestCheckResourceAttr(prefix, "name", data.Name+"-updated"),
resource.TestCheckResourceAttr(prefix, "issuer", data.Issuer),
resource.TestCheckResourceAttr(prefix, "subject", data.Subject),
),
},
},
})
}

func testServiceAccountIdentityConfig(localName string, localUserName string, data serviceaccounts.OIDCIdentity, userData users.User) string {
return fmt.Sprintf(`
resource "octopusdeploy_user" "%s" {
display_name = "%s"
email_address = "%s"
is_active = true
is_service = true
username = "%s"
}
resource "octopusdeploy_service_account_oidc_identity" "%s" {
name = "%s"
service_account_id = %s
issuer = "%s"
subject = "%s"
}`,
localUserName,
userData.DisplayName,
userData.EmailAddress,
userData.Username,
localName,
data.Name,
data.ServiceAccountID,
data.Issuer,
data.Subject)
}

func testServiceAccountIdentityUpdate(localName string, localUserName string, data serviceaccounts.OIDCIdentity, userData users.User) string {
data.Name = data.Name + "-updated"
return testServiceAccountIdentityConfig(localName, localUserName, data, userData)
}
Loading

0 comments on commit 39fe8de

Please sign in to comment.