Skip to content
Open
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
22 changes: 22 additions & 0 deletions docs/actions/mongodb_instance_snapshot_action.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
subcategory: "MongoDB"
page_title: "Scaleway: scaleway_mongodb_instance_snapshot_action"
---

# scaleway_mongodb_instance_snapshot_action (Action)

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

### Required

- `expires_at` (String) Expiration date of the snapshot in RFC3339 format (ISO 8601).
- `instance_id` (String) MongoDB instance ID to snapshot. Can be a plain UUID or a regional ID.

### Optional

- `name` (String) Name of the snapshot. If not set, a name will be generated.
- `region` (String) Region of the MongoDB instance. If not set, the region is derived from the instance_id when possible or from the provider configuration.
- `wait` (Boolean) Wait for the snapshot to reach a terminal state before returning.


207 changes: 207 additions & 0 deletions internal/services/mongodb/action_instance_snapshot_action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package mongodb

import (
"context"
"fmt"
"time"

"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/action"
"github.com/hashicorp/terraform-plugin-framework/action/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
mongodb "github.com/scaleway/scaleway-sdk-go/api/mongodb/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/meta"
)

var (
_ action.Action = (*InstanceSnapshotAction)(nil)
_ action.ActionWithConfigure = (*InstanceSnapshotAction)(nil)
)

// InstanceSnapshotAction creates a snapshot for a MongoDB instance.
type InstanceSnapshotAction struct {
mongodbAPI *mongodb.API
}

func (a *InstanceSnapshotAction) Configure(_ context.Context, req action.ConfigureRequest, resp *action.ConfigureResponse) {
if req.ProviderData == nil {
return
}

m, ok := req.ProviderData.(*meta.Meta)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Action Configure Type",
fmt.Sprintf("Expected *meta.Meta, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

a.mongodbAPI = newAPI(m)
}

func (a *InstanceSnapshotAction) Metadata(_ context.Context, req action.MetadataRequest, resp *action.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_mongodb_instance_snapshot_action"
}

type InstanceSnapshotActionModel struct {
InstanceID types.String `tfsdk:"instance_id"`
Region types.String `tfsdk:"region"`
Name types.String `tfsdk:"name"`
ExpiresAt types.String `tfsdk:"expires_at"`
Wait types.Bool `tfsdk:"wait"`
}

// NewInstanceSnapshotAction returns a new MongoDB instance snapshot action.
func NewInstanceSnapshotAction() action.Action {
return &InstanceSnapshotAction{}
}

func (a *InstanceSnapshotAction) Schema(_ context.Context, _ action.SchemaRequest, resp *action.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"instance_id": schema.StringAttribute{
Required: true,
Description: "MongoDB instance ID to snapshot. Can be a plain UUID or a regional ID.",
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
"region": schema.StringAttribute{
Optional: true,
Description: "Region of the MongoDB instance. If not set, the region is derived from the instance_id when possible or from the provider configuration.",
},
"name": schema.StringAttribute{
Optional: true,
Description: "Name of the snapshot. If not set, a name will be generated.",
},
"expires_at": schema.StringAttribute{
Optional: true,
Description: "Expiration date of the snapshot in RFC3339 format (ISO 8601). If not set, the snapshot will not expire.",
},
"wait": schema.BoolAttribute{
Optional: true,
Description: "Wait for the snapshot to reach a terminal state before returning.",
},
},
}
}

func (a *InstanceSnapshotAction) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
var data InstanceSnapshotActionModel

resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

if a.mongodbAPI == nil {
resp.Diagnostics.AddError(
"Unconfigured mongodbAPI",
"The action was not properly configured. The Scaleway client is missing. "+
"This is usually a bug in the provider. Please report it to the maintainers.",
)

return
}

if data.InstanceID.IsNull() || data.InstanceID.ValueString() == "" {
resp.Diagnostics.AddError(
"Missing instance_id",
"The instance_id attribute is required to create a MongoDB snapshot.",
)

return
}

instanceID := locality.ExpandID(data.InstanceID.ValueString())

var (
region scw.Region
err error
)

if !data.Region.IsNull() && data.Region.ValueString() != "" {
region = scw.Region(data.Region.ValueString())
} else {
// Try to derive region from the instance_id if it is a regional ID.
if derivedRegion, id, parseErr := regional.ParseID(data.InstanceID.ValueString()); parseErr == nil {
region = derivedRegion
instanceID = id
}
}

snapshotName := data.Name.ValueString()
if snapshotName == "" {
snapshotName = "tf-mongodb-snapshot"
}

var expirationTime *time.Time
if !data.ExpiresAt.IsNull() && data.ExpiresAt.ValueString() != "" {
expirationRaw := data.ExpiresAt.ValueString()

parsedTime, err := time.Parse(time.RFC3339, expirationRaw)
if err != nil {
resp.Diagnostics.AddError(
"Invalid expires_at value",
fmt.Sprintf("The expires_at attribute must be a valid RFC3339 timestamp. Got %q: %s", expirationRaw, err),
)

return
}

expirationTime = &parsedTime
}

createReq := &mongodb.CreateSnapshotRequest{
InstanceID: instanceID,
Name: snapshotName,
ExpiresAt: expirationTime,
}

if region != "" {
createReq.Region = region
}

snapshot, err := a.mongodbAPI.CreateSnapshot(createReq, scw.WithContext(ctx))
if err != nil {
resp.Diagnostics.AddError(
"Error executing MongoDB CreateSnapshot action",
fmt.Sprintf("Failed to create snapshot for instance %s: %s", instanceID, err),
)

return
}

if data.Wait.ValueBool() {
waitRegion := snapshot.Region
if waitRegion == "" && region != "" {
waitRegion = region
}

if waitRegion == "" {
resp.Diagnostics.AddError(
"Missing region for wait operation",
"Could not determine region to wait for MongoDB snapshot completion.",
)

return
}

_, err = waitForSnapshot(ctx, a.mongodbAPI, waitRegion, instanceID, snapshot.ID, defaultMongodbSnapshotTimeout)
if err != nil {
resp.Diagnostics.AddError(
"Error waiting for MongoDB snapshot completion",
fmt.Sprintf("Snapshot %s for instance %s did not reach a terminal state: %s", snapshot.ID, instanceID, err),
)

return
}
}
}
121 changes: 121 additions & 0 deletions internal/services/mongodb/action_instance_snapshot_action_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package mongodb_test

import (
"context"
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
mongodbSDK "github.com/scaleway/scaleway-sdk-go/api/mongodb/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional"
)

func TestAccActionMongoDBInstanceSnapshot_Basic(t *testing.T) {
if acctest.IsRunningOpenTofu() {
t.Skip("Skipping TestAccActionMongoDBInstanceSnapshot_Basic because actions are not yet supported on OpenTofu")
}

tt := acctest.NewTestTools(t)
defer tt.Cleanup()

resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: tt.ProviderFactories,
Steps: []resource.TestStep{
{
Config: `
resource "scaleway_mongodb_instance" "main" {
name = "test-mongodb-action-snapshot"
version = "7.0.12"
node_type = "MGDB-PLAY2-NANO"
node_number = 1
user_name = "my_initial_user"
password = "thiZ_is_v&ry_s3cret"

lifecycle {
action_trigger {
events = [after_create]
actions = [action.scaleway_mongodb_instance_snapshot_action.main]
}
}
}

action "scaleway_mongodb_instance_snapshot_action" "main" {
config {
instance_id = scaleway_mongodb_instance.main.id
name = "tf-acc-mongodb-instance-snapshot-action"
expires_at = "2026-11-01T00:00:00Z"
wait = true
}
}
`,
},
{
Config: `
resource "scaleway_mongodb_instance" "main" {
name = "test-mongodb-action-snapshot"
version = "7.0.12"
node_type = "MGDB-PLAY2-NANO"
node_number = 1
user_name = "my_initial_user"
password = "thiZ_is_v&ry_s3cret"

lifecycle {
action_trigger {
events = [after_create]
actions = [action.scaleway_mongodb_instance_snapshot_action.main]
}
}
}

action "scaleway_mongodb_instance_snapshot_action" "main" {
config {
instance_id = scaleway_mongodb_instance.main.id
name = "tf-acc-mongodb-instance-snapshot-action"
expires_at = "2026-11-01T00:00:00Z"
wait = true
}
}
`,
Check: resource.ComposeTestCheckFunc(
isSnapshotCreated(tt, "scaleway_mongodb_instance.main", "tf-acc-mongodb-instance-snapshot-action"),
),
},
},
})
}

func isSnapshotCreated(tt *acctest.TestTools, instanceResourceName, snapshotName string) resource.TestCheckFunc {
return func(state *terraform.State) error {
rs, ok := state.RootModule().Resources[instanceResourceName]
if !ok {
return fmt.Errorf("resource not found: %s", instanceResourceName)
}

instanceID := rs.Primary.ID
region, id, err := regional.ParseID(instanceID)
if err != nil {
return fmt.Errorf("failed to parse instance ID: %w", err)
}

api := mongodbSDK.NewAPI(tt.Meta.ScwClient())

snapshots, err := api.ListSnapshots(&mongodbSDK.ListSnapshotsRequest{
Region: region,
InstanceID: &id,
}, scw.WithAllPages(), scw.WithContext(context.Background()))
if err != nil {
return fmt.Errorf("failed to list snapshots: %w", err)
}

for _, snapshot := range snapshots.Snapshots {
if snapshot.Name == snapshotName {
return nil
}
}

return fmt.Errorf("snapshot with name %q not found for instance %s", snapshotName, instanceID)
}
}
Loading
Loading