Skip to content

Commit

Permalink
chore(cloudamqp_integration_aws_eventbridge): Migrate towards terrafo…
Browse files Browse the repository at this point in the history
…rm plugin framework

This is the first resource to move towards the new system and should serve as a template for all others.

The reason to choose this one as the first is because it is the smallest file that still has tests to prove
everything still works as before.
  • Loading branch information
tete17 committed Aug 19, 2024
1 parent 06faf5d commit 2e3e08d
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 117 deletions.
49 changes: 26 additions & 23 deletions cloudamqp/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,11 @@ func (p *cloudamqpProvider) DataSources(_ context.Context) []func() datasource.D
}

func (p *cloudamqpProvider) Resources(_ context.Context) []func() resource.Resource {
return []func() resource.Resource{}
return []func() resource.Resource{
func() resource.Resource {
return &awsEventBridgeResource{}
},
}
}

func New(version string, client *http.Client) provider.Provider {
Expand Down Expand Up @@ -145,28 +149,27 @@ func Provider(v string, client *http.Client) *schemaSdk.Provider {
"cloudamqp_vpc_info": dataSourceVpcInfo(),
},
ResourcesMap: map[string]*schemaSdk.Resource{
"cloudamqp_account_action": resourceAccountAction(),
"cloudamqp_alarm": resourceAlarm(),
"cloudamqp_custom_domain": resourceCustomDomain(),
"cloudamqp_extra_disk_size": resourceExtraDiskSize(),
"cloudamqp_instance": resourceInstance(),
"cloudamqp_integration_aws_eventbridge": resourceAwsEventBridge(),
"cloudamqp_integration_log": resourceIntegrationLog(),
"cloudamqp_integration_metric": resourceIntegrationMetric(),
"cloudamqp_node_actions": resourceNodeAction(),
"cloudamqp_notification": resourceNotification(),
"cloudamqp_plugin_community": resourcePluginCommunity(),
"cloudamqp_plugin": resourcePlugin(),
"cloudamqp_privatelink_aws": resourcePrivateLinkAws(),
"cloudamqp_privatelink_azure": resourcePrivateLinkAzure(),
"cloudamqp_rabbitmq_configuration": resourceRabbitMqConfiguration(),
"cloudamqp_security_firewall": resourceSecurityFirewall(),
"cloudamqp_upgrade_rabbitmq": resourceUpgradeRabbitMQ(),
"cloudamqp_vpc_connect": resourceVpcConnect(),
"cloudamqp_vpc_gcp_peering": resourceVpcGcpPeering(),
"cloudamqp_vpc_peering": resourceVpcPeering(),
"cloudamqp_vpc": resourceVpc(),
"cloudamqp_webhook": resourceWebhook(),
"cloudamqp_account_action": resourceAccountAction(),
"cloudamqp_alarm": resourceAlarm(),
"cloudamqp_custom_domain": resourceCustomDomain(),
"cloudamqp_extra_disk_size": resourceExtraDiskSize(),
"cloudamqp_instance": resourceInstance(),
"cloudamqp_integration_log": resourceIntegrationLog(),
"cloudamqp_integration_metric": resourceIntegrationMetric(),
"cloudamqp_node_actions": resourceNodeAction(),
"cloudamqp_notification": resourceNotification(),
"cloudamqp_plugin_community": resourcePluginCommunity(),
"cloudamqp_plugin": resourcePlugin(),
"cloudamqp_privatelink_aws": resourcePrivateLinkAws(),
"cloudamqp_privatelink_azure": resourcePrivateLinkAzure(),
"cloudamqp_rabbitmq_configuration": resourceRabbitMqConfiguration(),
"cloudamqp_security_firewall": resourceSecurityFirewall(),
"cloudamqp_upgrade_rabbitmq": resourceUpgradeRabbitMQ(),
"cloudamqp_vpc_connect": resourceVpcConnect(),
"cloudamqp_vpc_gcp_peering": resourceVpcGcpPeering(),
"cloudamqp_vpc_peering": resourceVpcPeering(),
"cloudamqp_vpc": resourceVpc(),
"cloudamqp_webhook": resourceWebhook(),
},
ConfigureFunc: configureClient(client),
}
Expand Down
271 changes: 177 additions & 94 deletions cloudamqp/resource_cloudamqp_aws_eventbridge.go
Original file line number Diff line number Diff line change
@@ -1,158 +1,241 @@
package cloudamqp

import (
"context"
"encoding/json"
"fmt"
"log"
"strconv"
"strings"

"github.com/cloudamqp/terraform-provider-cloudamqp/api"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"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/boolplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"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"
)

func resourceAwsEventBridge() *schema.Resource {
return &schema.Resource{
Create: resourceAwsEventBridgeCreate,
Read: resourceAwsEventBridgeRead,
Delete: resourceAwsEventBridgeDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"instance_id": {
Type: schema.TypeInt,
ForceNew: true,
type awsEventBridgeResource struct {
client *api.API
}

type awsEventBridgeResourceModel struct {
Id types.String `tfsdk:"id"`
InstanceID types.Int64 `tfsdk:"instance_id"`
AwsAccountId types.String `tfsdk:"aws_account_id"`
AwsRegion types.String `tfsdk:"aws_region"`
Vhost types.String `tfsdk:"vhost"`
QueueName types.String `tfsdk:"queue"`
WithHeaders types.Bool `tfsdk:"with_headers"`
Status types.String `tfsdk:"status"`
}

type awsEventBridgeResourceApiModel struct {
AwsAccountId string `json:"aws_account_id"`
AwsRegion string `json:"aws_region"`
Vhost string `json:"vhost"`
QueueName string `json:"queue"`
WithHeaders bool `json:"with_headers"`
}

func (r *awsEventBridgeResource) Configure(ctx context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
// Always perform a nil check when handling ProviderData because Terraform
// sets that data after it calls the ConfigureProvider RPC.
if request.ProviderData == nil {
return
}

client, ok := request.ProviderData.(*api.API)

if !ok {
response.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *api.API, got: %T. Please report this issue to the provider developers.", request.ProviderData),
)

return
}

r.client = client
}

func (r *awsEventBridgeResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
response.TypeName = "cloudamqp_integration_aws_eventbridge"
}

func (r *awsEventBridgeResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) {
response.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
"instance_id": schema.Int64Attribute{
Required: true,
Description: "Instance identifier",
PlanModifiers: []planmodifier.Int64{
int64planmodifier.RequiresReplace(),
},
},
"aws_account_id": {
Type: schema.TypeString,
ForceNew: true,
"aws_account_id": schema.StringAttribute{
Required: true,
Description: "The 12 digit AWS Account ID where you want the events to be sent to.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"aws_region": {
Type: schema.TypeString,
ForceNew: true,
"aws_region": schema.StringAttribute{
Required: true,
Description: "The AWS region where you the events to be sent to. (e.g. us-west-1, us-west-2, ..., etc.)",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"vhost": {
Type: schema.TypeString,
ForceNew: true,
"vhost": schema.StringAttribute{
Required: true,
Description: "The VHost the queue resides in.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"queue": {
Type: schema.TypeString,
ForceNew: true,
"queue": schema.StringAttribute{
Required: true,
Description: "A (durable) queue on your RabbitMQ instance.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"with_headers": {
Type: schema.TypeBool,
ForceNew: true,
"with_headers": schema.BoolAttribute{
Required: true,
Description: "Include message headers in the event data.",
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.RequiresReplace(),
},
},
"status": {
Type: schema.TypeString,
"status": schema.StringAttribute{
Computed: true,
Description: "Always set to null, unless there is an error starting the EventBridge",
},
},
}
}

func resourceAwsEventBridgeCreate(d *schema.ResourceData, meta interface{}) error {
var (
api = meta.(*api.API)
keys = awsEventbridgeAttributeKeys()
params = make(map[string]interface{})
instanceID = d.Get("instance_id").(int)
)
func (r *awsEventBridgeResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
var data awsEventBridgeResourceModel

// Read Terraform plan data into the model
response.Diagnostics.Append(request.Plan.Get(ctx, &data)...)

if response.Diagnostics.HasError() {
return
}

apiModel := awsEventBridgeResourceApiModel{
AwsAccountId: data.AwsAccountId.ValueString(),
AwsRegion: data.AwsRegion.ValueString(),
Vhost: data.Vhost.ValueString(),
QueueName: data.QueueName.ValueString(),
WithHeaders: data.WithHeaders.ValueBool(),
}

for _, k := range keys {
if v := d.Get(k); v != nil {
params[k] = v
}
var params map[string]interface{}
temp, err := json.Marshal(apiModel)
if err != nil {
response.Diagnostics.AddError(
"Unable to Create Resource",
"An unexpected error occurred while creating the resource create request. "+
"Please report this issue to the provider developers.\n\n"+
"JSON Error: "+err.Error(),
)
return
}
// TODO: This is totally a hack to get the struct into a map[string]interface{}
// It is very unlikely this will fail after the first one succeeds, so it should be fine to ignore the error
// Maybe after the api is moved into the repo we can improve the interface
_ = json.Unmarshal(temp, &params)

data, err := api.CreateAwsEventBridge(instanceID, params)
apiResponse, err := r.client.CreateAwsEventBridge(int(data.InstanceID.ValueInt64()), params)
if err != nil {
return err
response.Diagnostics.AddError(
"Failed to Create Resource",
"An error occurred while calling the api to create the surface, verify your permissions are correct.\n\n"+
"JSON Error: "+err.Error(),
)
return
}

d.SetId(data["id"].(string))
return nil
data.Id = types.StringValue(apiResponse["id"].(string))
data.Status = types.StringNull()

// Save data into Terraform state
response.Diagnostics.Append(response.State.Set(ctx, &data)...)
}

func resourceAwsEventBridgeRead(d *schema.ResourceData, meta interface{}) error {
if strings.Contains(d.Id(), ",") {
log.Printf("[DEBUG] cloudamqp::resource::aws-eventbridge::read id contains : %v", d.Id())
s := strings.Split(d.Id(), ",")
func (r *awsEventBridgeResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
var state awsEventBridgeResourceModel

// Read Terraform plan data into the model
response.Diagnostics.Append(request.State.Get(ctx, &state)...)

if strings.Contains(state.Id.ValueString(), ",") {
log.Printf("[DEBUG] cloudamqp::resource::aws-eventbridge::read id contains : %v", state.Id.String())
s := strings.Split(state.Id.ValueString(), ",")
log.Printf("[DEBUG] cloudamqp::resource::aws-eventbridge::read split ids: %v, %v", s[0], s[1])
d.SetId(s[0])
state.Id = types.StringValue(s[0])
instanceID, _ := strconv.Atoi(s[1])
d.Set("instance_id", instanceID)
state.InstanceID = types.Int64Value(int64(instanceID))
}
if d.Get("instance_id").(int) == 0 {
return fmt.Errorf("missing instance identifier: {resource_id},{instance_id}")
if state.InstanceID.ValueInt64() == 0 {
response.Diagnostics.AddError("Missing instance identifier {resource_id},{instance_id}", "")
return
}

var (
api = meta.(*api.API)
instanceID = d.Get("instance_id").(int)
id = state.Id.ValueString()
instanceID = int(state.InstanceID.ValueInt64())
)

log.Printf("[DEBUG] cloudamqp::resource::aws-eventbridge::read ID: %v, instanceID %v", d.Id(), instanceID)
data, err := api.ReadAwsEventBridge(instanceID, d.Id())
log.Printf("[DEBUG] cloudamqp::resource::aws-eventbridge::read ID: %v, instanceID %v", id, instanceID)
data, err := r.client.ReadAwsEventBridge(instanceID, id)
if err != nil {
return err
response.Diagnostics.AddError("Something went wrong while reading the aws event bridge", fmt.Sprintf("%v", err))
return
}

for k, v := range data {
if validateAwsEventBridgeSchemaAttribute(k) {
if v == nil {
continue
}
if err = d.Set(k, v); err != nil {
return fmt.Errorf("error setting %s for resource %s: %s", k, d.Id(), err)
}
}
}
state.AwsAccountId = types.StringValue(data["aws_account_id"].(string))
state.AwsRegion = types.StringValue(data["aws_region"].(string))
state.Vhost = types.StringValue(data["vhost"].(string))
state.QueueName = types.StringValue(data["queue"].(string))
state.WithHeaders = types.BoolValue(data["with_headers"].(bool))

return nil
}
// Save data into Terraform state
response.Diagnostics.Append(response.State.Set(ctx, &state)...)

func resourceAwsEventBridgeDelete(d *schema.ResourceData, meta interface{}) error {
var (
api = meta.(*api.API)
instanceID = d.Get("instance_id").(int)
)
return
}

return api.DeleteAwsEventBridge(instanceID, d.Id())
func (r *awsEventBridgeResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
// This resource does not implement the Update function
}

func awsEventbridgeAttributeKeys() []string {
return []string{
"aws_account_id",
"aws_region",
"vhost",
"queue",
"with_headers",
func (r *awsEventBridgeResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
var data awsEventBridgeResourceModel

// Read Terraform plan data into the model
response.Diagnostics.Append(request.State.Get(ctx, &data)...)
var id = data.Id.ValueString()
err := r.client.DeleteAwsEventBridge(int(data.InstanceID.ValueInt64()), id)

if err != nil {
response.Diagnostics.AddError("An error occurred while deleting cloudamqp_integration_aws_eventbridge",
fmt.Sprintf("Error deleting Cloudamqp event bridge %s: %s", id, err),
)
}
}

func validateAwsEventBridgeSchemaAttribute(key string) bool {
switch key {
case "aws_account_id",
"aws_region",
"vhost",
"queue",
"with_headers",
"status":
return true
}
return false
func (r *awsEventBridgeResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response)
}

0 comments on commit 2e3e08d

Please sign in to comment.