Skip to content

Commit

Permalink
[INF-5307] - Add the MongoDB Ably Ingress Rule
Browse files Browse the repository at this point in the history
This adds the new Ably Ingress rule for MongoDB.
  • Loading branch information
graham-russell committed Dec 13, 2024
1 parent 0215957 commit 1341ff7
Show file tree
Hide file tree
Showing 14 changed files with 584 additions and 12 deletions.
44 changes: 44 additions & 0 deletions docs/resources/ingress_rule_mongodb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "ably_ingress_rule_mongodb Resource - terraform-provider-ably"
subcategory: ""
description: |-
The ably_ingress_rule_mongodb resource allows you to create and manage an Ably Ingress rule for MongoDB. Read more at
---

# ably_ingress_rule_mongodb (Resource)

The `ably_ingress_rule_mongodb` resource allows you to create and manage an Ably Ingress rule for MongoDB. Read more at



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

### Required

- `app_id` (String) The Ably application ID.
- `target` (Attributes) object (rule_source) (see [below for nested schema](#nestedatt--target))

### Optional

- `status` (String) The status of the rule. Rules can be enabled or disabled.

### Read-Only

- `id` (String) The rule ID.

<a id="nestedatt--target"></a>
### Nested Schema for `target`

Required:

- `collection` (String)
- `database` (String)
- `full_document` (String)
- `full_document_before_change` (String)
- `pipeline` (String)
- `primary_site` (String)
- `url` (String)


4 changes: 2 additions & 2 deletions docs/resources/rule_azure_function.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ resource "ably_rule_azure_function" "rule0" {
}
request_mode = "batch"
target = {
azure_app_id = "coms",
azure_app_id = "demo",
function_name = "function0"
headers = [
{
Expand Down Expand Up @@ -90,4 +90,4 @@ Optional:
Required:

- `name` (String) The name of the header
- `value` (String) The value of the header
- `value` (String) The value of the header
2 changes: 1 addition & 1 deletion examples/playground/rule_http_azure_function.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ resource "ably_rule_azure_function" "rule0" {
}
request_mode = "batch"
target = {
azure_app_id = "coms",
azure_app_id = "demo",
function_name = "function0"
headers = [
{
Expand Down
2 changes: 1 addition & 1 deletion examples/resources/rule_http_azure_function.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ resource "ably_rule_azure_function" "rule0" {
}
request_mode = "batch"
target = {
azure_app_id = "coms",
azure_app_id = "demo",
function_name = "function0"
headers = [
{
Expand Down
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/ably/terraform-provider-ably
go 1.19

require (
github.com/ably/ably-control-go v0.4.0
github.com/ably/ably-control-go v0.4.1-0.20241211161828-60786a2a414b
github.com/hashicorp/terraform-plugin-docs v0.13.0
github.com/hashicorp/terraform-plugin-framework v0.16.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.21.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ github.com/ably/ably-control-go v0.3.0 h1:m5Y2SHE69Mwg8iDASZxqmlHFSwpejN4s2TgZi3
github.com/ably/ably-control-go v0.3.0/go.mod h1:TP7gWAy+ga++gX6OZ0DtjwH8oVKKdiaIGQvZvxDKNdk=
github.com/ably/ably-control-go v0.4.0 h1:JouYcHKT2TvvAGPpEPQJcFo5p9k1KfLUr/k9bfy+tYI=
github.com/ably/ably-control-go v0.4.0/go.mod h1:TP7gWAy+ga++gX6OZ0DtjwH8oVKKdiaIGQvZvxDKNdk=
github.com/ably/ably-control-go v0.4.1-0.20241211161828-60786a2a414b h1:yHdqAi4zYDFIGmzv7WUPwA8JnLwaL5ExFys85Mf0Ih0=
github.com/ably/ably-control-go v0.4.1-0.20241211161828-60786a2a414b/go.mod h1:D5cHDJdYVHXdxfRwypsLaTySGE/bT3H0wlgxwBWCbxQ=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
Expand Down
297 changes: 297 additions & 0 deletions internal/provider/ingress_rules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
package ably_control

import (
"context"
"fmt"
"strings"

ably_control_go "github.com/ably/ably-control-go"
"github.com/hashicorp/terraform-plugin-framework/path"
tfsdk_resource "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
)

// converts ingress rule from terraform format to control sdk format
func GetPlanIngressRule(plan AblyIngressRule) ably_control_go.NewIngressRule {
var target ably_control_go.IngressTarget

switch t := plan.Target.(type) {
case *AblyIngressRuleTargetMongo:
target = &ably_control_go.IngressMongoTarget{
Url: t.Url,
Database: t.Database,
Collection: t.Collection,
Pipeline: t.Pipeline,
FullDocument: t.FullDocument,
FullDocumentBeforeChange: t.FullDocumentBeforeChange,
PrimarySite: t.PrimarySite,
}
}

rule_values := ably_control_go.NewIngressRule{
Status: plan.Status.ValueString(),
Target: target,
}

return rule_values
}

// Maps response body to resource schema attributes.
// Using plan to fill in values that the api does not return.
func GetIngressRuleResponse(ably_ingress_rule *ably_control_go.IngressRule, plan *AblyIngressRule) AblyIngressRule {
var resp_target interface{}

switch v := ably_ingress_rule.Target.(type) {
case *ably_control_go.IngressMongoTarget:

resp_target = &AblyIngressRuleTargetMongo{
Url: v.Url,
Database: v.Database,
Collection: v.Collection,
Pipeline: v.Pipeline,
FullDocument: v.FullDocument,
FullDocumentBeforeChange: v.FullDocumentBeforeChange,
PrimarySite: v.PrimarySite,
}
}

resp_rule := AblyIngressRule{
ID: types.StringValue(ably_ingress_rule.ID),
AppID: types.StringValue(ably_ingress_rule.AppID),
Status: types.StringValue(ably_ingress_rule.Status),
Target: resp_target,
}

return resp_rule
}

func GetIngressRuleSchema(target map[string]tfsdk.Attribute, markdown_description string) tfsdk.Schema {
return tfsdk.Schema{
MarkdownDescription: markdown_description,
Attributes: map[string]tfsdk.Attribute{
"id": {
Type: types.StringType,
Computed: true,
Description: "The rule ID.",
PlanModifiers: []tfsdk.AttributePlanModifier{
tfsdk_resource.UseStateForUnknown(),
},
},
"app_id": {
Type: types.StringType,
Required: true,
Description: "The Ably application ID.",
PlanModifiers: []tfsdk.AttributePlanModifier{
tfsdk_resource.RequiresReplace(),
},
},
"status": {
Type: types.StringType,
Optional: true,
Description: "The status of the rule. Rules can be enabled or disabled.",
},
"target": {
Required: true,
Description: "object (rule_source)",
Attributes: tfsdk.SingleNestedAttributes(target),
},
},
}
}

type IngressRule interface {
Provider() *provider
Name() string
}

// Create a new resource
func CreateIngressRule[T any](r IngressRule, ctx context.Context, req tfsdk_resource.CreateRequest, resp *tfsdk_resource.CreateResponse) {
// Checks whether the provider and API Client are configured. If they are not, the provider responds with an error.
if !r.Provider().configured {
resp.Diagnostics.AddError(
"Provider not configured",
"The provider hasn't been configured before apply",
)
return
}

// Gets plan values
var p AblyIngressRuleDecoder[*T]
diags := req.Plan.Get(ctx, &p)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

plan := p.IngressRule()
plan_values := GetPlanIngressRule(plan)

// Creates a new Ably Ingress Rule by invoking the CreateRule function from the Client Library
ingress_rule, err := r.Provider().client.CreateIngressRule(plan.AppID.ValueString(), &plan_values)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error creating Resource '%s'", r.Name()),
fmt.Sprintf("Could not create resource '%s', unexpected error: %s", r.Name(), err.Error()),
)

return
}

response_values := GetIngressRuleResponse(&ingress_rule, &plan)

// Sets state for the new Ably Ingress Rule.
diags = resp.State.Set(ctx, response_values)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}

// Read resource
func ReadIngressRule[T any](r IngressRule, ctx context.Context, req tfsdk_resource.ReadRequest, resp *tfsdk_resource.ReadResponse) {
// Gets the current state. If it is unable to, the provider responds with an error.
var s AblyIngressRuleDecoder[*T]
diags := req.State.Get(ctx, &s)
resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}

state := s.IngressRule()

// Gets the Ably App ID and Ably Ingress Rule ID value for the resource
app_id := s.AppID.ValueString()
ingress_rule_id := s.ID.ValueString()

// Get Ingress Rule data
ingress_rule, err := r.Provider().client.IngressRule(app_id, ingress_rule_id)

if err != nil {
if is_404(err) {
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.AddError(
fmt.Sprintf("Error deleting Resource %s", r.Name()),
fmt.Sprintf("Could not delete resource %s, unexpected error: %s", r.Name(), err.Error()),
)
return
}

response_values := GetIngressRuleResponse(&ingress_rule, &state)

// Sets state to app values.
diags = resp.State.Set(ctx, &response_values)

resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}
}

// // Update resource
func UpdateIngressRule[T any](r IngressRule, ctx context.Context, req tfsdk_resource.UpdateRequest, resp *tfsdk_resource.UpdateResponse) {
// Gets plan values
var p AblyIngressRuleDecoder[*T]
diags := req.Plan.Get(ctx, &p)
resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}

plan := p.IngressRule()

rule_values := GetPlanIngressRule(plan)

// Gets the Ably App ID and Ably Ingress Rule ID value for the resource
app_id := plan.AppID.ValueString()
rule_id := plan.ID.ValueString()

// Update Ably Ingress Rule
ingress_rule, err := r.Provider().client.UpdateIngressRule(app_id, rule_id, &rule_values)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error updading Resource %s", r.Name()),
fmt.Sprintf("Could not update resource %s, unexpected error: %s", r.Name(), err.Error()),
)
return
}

response_values := GetIngressRuleResponse(&ingress_rule, &plan)

// Sets state to app values.
diags = resp.State.Set(ctx, &response_values)

resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}
}

// Delete resource
func DeleteIngressRule[T any](r IngressRule, ctx context.Context, req tfsdk_resource.DeleteRequest, resp *tfsdk_resource.DeleteResponse) {
// Gets the current state. If it is unable to, the provider responds with an error.
var s AblyIngressRuleDecoder[*T]
diags := req.State.Get(ctx, &s)
resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}

state := s.IngressRule()

// Gets the Ably App ID and Ably Rule ID value for the resource
app_id := state.AppID.ValueString()
ingress_rule_id := state.ID.ValueString()

err := r.Provider().client.DeleteIngressRule(app_id, ingress_rule_id)
if err != nil {
if is_404(err) {
resp.Diagnostics.AddWarning(
fmt.Sprintf("Resource does %s not exist", r.Name()),
fmt.Sprintf("Resource does %s not exist, it may have already been deleted: %s", r.Name(), err.Error()),
)
} else {
resp.Diagnostics.AddError(
fmt.Sprintf("Error deleting Resource %s'", r.Name()),
fmt.Sprintf("Could not delete resource '%s', unexpected error: %s", r.Name(), err.Error()),
)
return
}
}

// Remove resource from state
resp.State.RemoveResource(ctx)
}

// // Import resource
func ImportIngressRuleResource(ctx context.Context, req tfsdk_resource.ImportStateRequest, resp *tfsdk_resource.ImportStateResponse, fields ...string) {
// Save the import identifier in the id attribute
// identifier should be in the format app_id,key_id
idParts := strings.Split(req.ID, ",")
anyEmpty := false

for _, v := range idParts {
if v == "" {
anyEmpty = true
}
}

if len(idParts) != len(fields) || anyEmpty {
resp.Diagnostics.AddError(
"Unexpected Import Identifier",
fmt.Sprintf("Expected import identifier with format: '%s'. Got: %q", strings.Join(fields, ","), req.ID),
)
return
}
// Recent PR in TF Plugin Framework for paths but Hashicorp examples not updated - https://github.com/hashicorp/terraform-plugin-framework/pull/390
for i, v := range fields {
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root(v), idParts[i])...)
}
}
Loading

0 comments on commit 1341ff7

Please sign in to comment.