Skip to content

Commit

Permalink
feat(authorization): service account resource without permissions (#97)
Browse files Browse the repository at this point in the history
* feat(authorization): service account resource without permissions

* fix(authorization): service account terraform test resources names
  • Loading branch information
ndopj authored Feb 28, 2024
1 parent ed27a9f commit b507c54
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 3 deletions.
32 changes: 32 additions & 0 deletions client/monte_carlo_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,35 @@ type CreateOrUpdateComparisonRule struct {
}
} `graphql:"createOrUpdateComparisonRule(comparisons: $comparisons, customRuleUuid: $customRuleUuid, description: $description, queryResultType: $queryResultType, scheduleConfig: $scheduleConfig, sourceConnectionId: $sourceConnectionId, sourceDwId: $sourceDwId, sourceSqlQuery: $sourceSqlQuery, targetConnectionId: $targetConnectionId, targetDwId: $targetDwId, targetSqlQuery: $targetSqlQuery)"`
}

type CreateOrUpdateServiceApiToken struct {
CreateOrUpdateServiceApiToken struct {
AccessToken struct {
Id string
Token string
}
} `graphql:"createOrUpdateServiceApiToken(comment: $comment, displayName: $displayName, expirationInDays: $expirationInDays, groups: $groups, tokenId: $tokenId)"`
}

type TokenMetadata struct {
Id string
Comment string
CreatedBy string
CreationTime string
Email string
ExpirationTime string
FirstName string
LastName string
Groups []string
IsServiceApiToken bool
}

type GetTokenMetadata struct {
GetTokenMetadata []TokenMetadata `graphql:"getTokenMetadata(index: $index, isServiceApiToken: $isServiceApiToken)"`
}

type DeleteAccessToken struct {
DeleteAccessToken struct {
Success bool
} `graphql:"deleteAccessToken(tokenId: $tokenId)"`
}
182 changes: 182 additions & 0 deletions internal/authorization/service_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package authorization

import (
"context"
"fmt"
"slices"

"github.com/kiwicom/terraform-provider-montecarlo/client"
"github.com/kiwicom/terraform-provider-montecarlo/internal/common"

"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/types"
)

// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = &ServiceAccountResource{}

// This resource cannot be imported, since Token cannot be retrieved from Monte Carlo API.
// var _ resource.ResourceWithImportState = &ServiceAccountResource{}

// To simplify provider implementations, a named function can be created with the resource implementation.
func NewServiceAccountResource() resource.Resource {
return &ServiceAccountResource{}
}

// ServiceAccountResource defines the resource implementation.
type ServiceAccountResource struct {
client client.MonteCarloClient
}

// ServiceAccountResourceModel describes the resource data model according to its Schema.
type ServiceAccountResourceModel struct {
Id types.String `tfsdk:"id"`
Token types.String `tfsdk:"token"`
Description types.String `tfsdk:"description"`
}

func (r *ServiceAccountResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_service_account"
}

func (r *ServiceAccountResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
Optional: false,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"token": schema.StringAttribute{
Computed: true,
Optional: false,
Sensitive: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"description": schema.StringAttribute{
Computed: true,
Optional: true,
Default: stringdefault.StaticString(""),
},
},
}
}

func (r *ServiceAccountResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
client, diags := common.Configure(req)
resp.Diagnostics.Append(diags...)
r.client = client
}

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

createResult := client.CreateOrUpdateServiceApiToken{}
variables := map[string]interface{}{
"tokenId": (*string)(nil),
"comment": data.Description.ValueString(),
"displayName": (*string)(nil),
"expirationInDays": (*int)(nil),
"groups": (*[]string)(nil),
}

if err := r.client.Mutate(ctx, &createResult, variables); err != nil {
to_print := fmt.Sprintf("MC client 'CreateOrUpdateServiceApiToken' mutation result - %s", err.Error())
resp.Diagnostics.AddError(to_print, "")
return
}

data.Id = types.StringValue(createResult.CreateOrUpdateServiceApiToken.AccessToken.Id)
data.Token = types.StringValue(createResult.CreateOrUpdateServiceApiToken.AccessToken.Token)
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

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

var index int
type AccessKeyIndexEnum string
readResult := client.GetTokenMetadata{}
variables := map[string]interface{}{
"index": (AccessKeyIndexEnum)("account"),
"isServiceApiToken": true,
}

if err := r.client.Query(ctx, &readResult, variables); err != nil {
to_print := fmt.Sprintf("MC client 'GetTokenMetadata' query result - %s", err.Error())
resp.Diagnostics.AddError(to_print, "")
return
}

if index = slices.IndexFunc(readResult.GetTokenMetadata, func(token client.TokenMetadata) bool {
return token.Id == data.Id.ValueString()
}); index < 0 {
to_print := fmt.Sprintf("Token [ID: %s] not found", data.Id.ValueString())
resp.Diagnostics.AddWarning(to_print, "")
resp.State.RemoveResource(ctx)
return
}

data.Description = types.StringValue(readResult.GetTokenMetadata[index].Comment)
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

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

updateResult := client.CreateOrUpdateServiceApiToken{}
variables := map[string]interface{}{
"tokenId": data.Id.ValueString(),
"comment": data.Description.ValueString(),
"displayName": (*string)(nil),
"expirationInDays": (*int)(nil),
"groups": (*[]string)(nil),
}

if err := r.client.Mutate(ctx, &updateResult, variables); err == nil {
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
} else {
to_print := fmt.Sprintf("MC client 'CreateOrUpdateServiceApiToken' mutation result - %s", err.Error())
resp.Diagnostics.AddError(to_print, "")
}
}

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

deleteResult := client.DeleteAccessToken{}
variables := map[string]interface{}{"tokenId": data.Id.ValueString()}

if err := r.client.Mutate(ctx, &deleteResult, variables); err != nil {
to_print := fmt.Sprintf("MC client 'DeleteAccessToken' mutation result - %s", err.Error())
resp.Diagnostics.AddError(to_print, "")
} else if !deleteResult.DeleteAccessToken.Success {
toPrint := "MC client 'DeleteAccessToken' mutation - success = false, " +
"service account probably already doesn't exists. This resource will continue with its deletion"
resp.Diagnostics.AddWarning(toPrint, "")
}
}
44 changes: 44 additions & 0 deletions internal/authorization/service_account_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package authorization_test

import (
"os"
"testing"

"github.com/kiwicom/terraform-provider-montecarlo/internal/acctest"

"github.com/hashicorp/terraform-plugin-testing/config"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func TestAccServiceAccountResource(t *testing.T) {
mc_api_key_id := os.Getenv("MC_API_KEY_ID")
mc_api_key_token := os.Getenv("MC_API_KEY_TOKEN")

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.TestAccPreCheck(t) },
Steps: []resource.TestStep{
{ // Create and Read testing
ProtoV6ProviderFactories: acctest.TestAccProviderFactories,
ConfigFile: config.TestNameFile("create.tf"),
ConfigVariables: config.Variables{
"montecarlo_api_key_id": config.StringVariable(mc_api_key_id),
"montecarlo_api_key_token": config.StringVariable(mc_api_key_token),
},
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("montecarlo_service_account.test", "description", ""),
),
},
{ // Update and Read testing
ProtoV6ProviderFactories: acctest.TestAccProviderFactories,
ConfigFile: config.TestNameFile("update.tf"),
ConfigVariables: config.Variables{
"montecarlo_api_key_id": config.StringVariable(mc_api_key_id),
"montecarlo_api_key_token": config.StringVariable(mc_api_key_token),
},
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("montecarlo_service_account.test", "description", "sa-test"),
),
},
},
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
variable "montecarlo_api_key_id" {
type = string
}

variable "montecarlo_api_key_token" {
type = string
}

provider "montecarlo" {
account_service_key = {
id = var.montecarlo_api_key_id # (secret)
token = var.montecarlo_api_key_token # (secret)
}
}

resource "montecarlo_service_account" "test" {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
variable "montecarlo_api_key_id" {
type = string
}

variable "montecarlo_api_key_token" {
type = string
}

provider "montecarlo" {
account_service_key = {
id = var.montecarlo_api_key_id # (secret)
token = var.montecarlo_api_key_token # (secret)
}
}

resource "montecarlo_service_account" "test" {
description = "sa-test"
}
1 change: 0 additions & 1 deletion internal/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,6 @@ func (r *DomainResource) Delete(ctx context.Context, req resource.DeleteRequest,
if err := r.client.Mutate(ctx, &deleteResult, variables); err != nil {
toPrint := fmt.Sprintf("MC client 'DeleteDomain' mutation result - %s", err.Error())
resp.Diagnostics.AddError(toPrint, "")
return
} else if deleteResult.DeleteDomain.Deleted != 1 {
toPrint := fmt.Sprintf("MC client 'DeleteDomain' mutation - deleted = %d, "+
"expected result is 1 - more domains might have been deleted. This resource "+
Expand Down
1 change: 1 addition & 0 deletions internal/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func (p *Provider) Resources(ctx context.Context) []func() resource.Resource {
authorization.NewIamGroupResource,
authorization.NewIamMemberResource,
//monitor.NewComparisonMonitorResource,
authorization.NewServiceAccountResource,
}
}

Expand Down
1 change: 0 additions & 1 deletion internal/warehouse/bigquery_warehouse.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,6 @@ func (r *BigQueryWarehouseResource) Delete(ctx context.Context, req resource.Del
if err := r.client.Mutate(ctx, &removeResult, variables); err != nil {
toPrint := fmt.Sprintf("MC client 'RemoveConnection' mutation result - %s", err.Error())
resp.Diagnostics.AddError(toPrint, "")
return
} else if !removeResult.RemoveConnection.Success {
toPrint := "MC client 'RemoveConnection' mutation - success = false, " +
"connection probably already doesn't exists. This resource will continue with its deletion"
Expand Down
1 change: 0 additions & 1 deletion internal/warehouse/transactional_warehouse.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,6 @@ func (r *TransactionalWarehouseResource) Delete(ctx context.Context, req resourc
if err := r.client.Mutate(ctx, &removeResult, variables); err != nil {
toPrint := fmt.Sprintf("MC client 'RemoveConnection' mutation result - %s", err.Error())
resp.Diagnostics.AddError(toPrint, "")
return
} else if !removeResult.RemoveConnection.Success {
toPrint := "MC client 'RemoveConnection' mutation - success = false, " +
"connection probably already doesn't exists. This resource will continue with its deletion"
Expand Down

0 comments on commit b507c54

Please sign in to comment.