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
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// See entire project at https://github.com/mongodb/atlas-architecture-go-sdk
package main

import (
"context"
"fmt"
"log"
"time"

"atlas-sdk-go/internal/auth"
"atlas-sdk-go/internal/config"
"atlas-sdk-go/internal/data/recovery"
"atlas-sdk-go/internal/typeutils"

"github.com/joho/godotenv"
"go.mongodb.org/atlas-sdk/v20250219001/admin"
)

const (
scenarioRegionalOutage = "regional-outage"
scenarioDataDeletion = "data-deletion"
)

func main() {
envFile := ".env.production"
if err := godotenv.Load(envFile); err != nil {
log.Printf("Warning: could not load %s file: %v", envFile, err)
}

secrets, cfg, err := config.LoadAllFromEnv()
if err != nil {
log.Fatalf("Failed to load configuration %v", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer cancel()
client, err := auth.NewClient(ctx, cfg, secrets)
if err != nil {
log.Fatalf("Failed to initialize authentication client: %v", err)
}

opts, err := recovery.LoadDROptionsFromEnv(cfg.ProjectID)
if err != nil {
log.Fatalf("Configuration error: %v", err)
}

fmt.Printf("Starting disaster recovery scenario: %s\nProject: %s\nCluster: %s\n", opts.Scenario, opts.ProjectID, opts.ClusterName)

if opts.DryRun {
fmt.Println("DRY RUN: no write operations will be performed")
}

var summary string
var opErr error

switch opts.Scenario {
case scenarioRegionalOutage:
summary, opErr = simulateRegionalOutage(ctx, client, opts)
case scenarioDataDeletion:
summary, opErr = executeDataDeletionRestore(ctx, client, opts)
default:
opErr = fmt.Errorf("unsupported DR_SCENARIO '%s'", opts.Scenario)
}

if opErr != nil {
log.Fatalf("Scenario failed: %v", opErr)
}

fmt.Println("\n=== Summary ===")
fmt.Println(summary)
fmt.Println("Disaster recovery procedure completed.")
}

// executeDataDeletionRestore initiates a restore job for a specified snapshot in a MongoDB Atlas cluster.
func executeDataDeletionRestore(ctx context.Context, client *admin.APIClient, o recovery.DrOptions) (string, error) {
job := admin.DiskBackupSnapshotRestoreJob{SnapshotId: &o.SnapshotID, TargetClusterName: &o.ClusterName}
if o.DryRun {
return fmt.Sprintf("(dry-run) Would submit restore job for snapshot %s", o.SnapshotID), nil
}
_, _, err := client.CloudBackupsApi.CreateBackupRestoreJob(ctx, o.ProjectID, o.ClusterName, &job).Execute()
if err != nil {
return "", fmt.Errorf("create restore job: %w", err)
}
return fmt.Sprintf("Restore job submitted for snapshot %s", o.SnapshotID), nil
}

// simulateRegionalOutage modifies the electable node count in a target region for a MongoDB Atlas cluster.
func simulateRegionalOutage(ctx context.Context, client *admin.APIClient, o recovery.DrOptions) (string, error) {
cluster, _, err := client.ClustersApi.GetCluster(ctx, o.ProjectID, o.ClusterName).Execute()
if err != nil {
return "", fmt.Errorf("get cluster: %w", err)
}
if !cluster.HasReplicationSpecs() {
return "", fmt.Errorf("cluster has no replication specs")
}
repl := cluster.GetReplicationSpecs()
addedNodes, foundTarget := recovery.AddElectableNodesToRegion(repl, o.TargetRegion, o.AddNodes)
if !foundTarget {
return "", fmt.Errorf("target region '%s' not found in replication specs", o.TargetRegion)
}
zeroedRegions := 0
if o.OutageRegion != "" {
zeroedRegions = recovery.ZeroElectableNodesInRegion(repl, o.OutageRegion)
}
payload := admin.NewClusterDescription20240805()
payload.SetReplicationSpecs(repl)
if o.DryRun {
return fmt.Sprintf("(dry-run) Would add %d electable nodes to %s%s", addedNodes, o.TargetRegion, typeutils.SuffixZeroed(zeroedRegions, o.OutageRegion)), nil
}
_, _, err = client.ClustersApi.UpdateCluster(ctx, o.ProjectID, o.ClusterName, payload).Execute()
if err != nil {
return "", fmt.Errorf("update cluster: %w", err)
}
return fmt.Sprintf("Added %d electable nodes to %s%s", addedNodes, o.TargetRegion, typeutils.SuffixZeroed(zeroedRegions, o.OutageRegion)), nil
}

22 changes: 16 additions & 6 deletions generated-usage-examples/go/atlas-sdk-go/project-copy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Currently, the repository includes examples that demonstrate the following:
- Return all linked organizations from a specific billing organization
- Get historical invoices for an organization
- Programmatically archive Atlas cluster data
- Perform disaster recovery operations (e.g. restore from snapshot)

As the Architecture Center documentation evolves, this repository will be updated with new examples
and improvements to existing code.
Expand All @@ -29,7 +30,8 @@ and improvements to existing code.
├── examples # Runnable examples by category
│ ├── billing/
│ ├── monitoring/
│ └── performance/
│ ├── performance/
│ └── recovery/
├── configs # Atlas configuration template
│ └── config.example.json
├── internal # Shared utilities and helpers
Expand All @@ -42,7 +44,8 @@ and improvements to existing code.
│ ├── errors/
│ ├── fileutils/
│ ├── logs/
│ └── metrics/
│ ├── metrics/
│ └── typeutils/
├── go.mod
├── go.sum
├── CHANGELOG.md # List of major changes to the project
Expand All @@ -61,10 +64,10 @@ and improvements to existing code.

1. Create a `.env.<environment>` file in the root directory with your MongoDB Atlas service account credentials. For example, create a `.env.development` file for your dev environment:
```dotenv
MONGODB_ATLAS_SERVICE_ACCOUNT_ID=<your_service_account_id>
MONGODB_ATLAS_SERVICE_ACCOUNT_SECRET=<your_service_account_secret>
ATLAS_DOWNLOADS_DIR="tmp/atlas_downloads" # optional download directory
CONFIG_PATH="configs/config.development.json" # optional path to Atlas config file
MONGODB_ATLAS_SERVICE_ACCOUNT_ID=<your_service_account_id>
MONGODB_ATLAS_SERVICE_ACCOUNT_SECRET=<your_service_account_secret>
ATLAS_DOWNLOADS_DIR="tmp/atlas_downloads" # optional download directory
CONFIG_PATH="configs/config.development.json" # optional path to Atlas config file
```
> **NOTE:** For production, use a secrets manager (e.g. HashiCorp Vault, AWS Secrets Manager)
> instead of environment variables.
Expand Down Expand Up @@ -133,6 +136,13 @@ go run examples/monitoring/metrics_process/main.go
go run examples/performance/archiving/main.go
```

### Recovery

#### Perform Disaster Recovery Operations
```bash
go run examples/performance/recovery/main.go
```

## Changelog

For list of major changes to this project, see [CHANGELOG](CHANGELOG.md).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package main

import (
"context"
"fmt"
"log"
"time"

"atlas-sdk-go/internal/auth"
"atlas-sdk-go/internal/config"
"atlas-sdk-go/internal/data/recovery"
"atlas-sdk-go/internal/typeutils"

"github.com/joho/godotenv"
"go.mongodb.org/atlas-sdk/v20250219001/admin"
)

const (
scenarioRegionalOutage = "regional-outage"
scenarioDataDeletion = "data-deletion"
)

func main() {
envFile := ".env.production"
if err := godotenv.Load(envFile); err != nil {
log.Printf("Warning: could not load %s file: %v", envFile, err)
}

secrets, cfg, err := config.LoadAllFromEnv()
if err != nil {
log.Fatalf("Failed to load configuration %v", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer cancel()
client, err := auth.NewClient(ctx, cfg, secrets)
if err != nil {
log.Fatalf("Failed to initialize authentication client: %v", err)
}

opts, err := recovery.LoadDROptionsFromEnv(cfg.ProjectID)
if err != nil {
log.Fatalf("Configuration error: %v", err)
}

fmt.Printf("Starting disaster recovery scenario: %s\nProject: %s\nCluster: %s\n", opts.Scenario, opts.ProjectID, opts.ClusterName)

if opts.DryRun {
fmt.Println("DRY RUN: no write operations will be performed")
}

var summary string
var opErr error

switch opts.Scenario {
case scenarioRegionalOutage:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything looks good here in terms of output files, and I was able to build and execute this new example, but same caveat applies that I wasn't able to fully complete either scenario due to configuration constraints.

summary, opErr = simulateRegionalOutage(ctx, client, opts)
case scenarioDataDeletion:
summary, opErr = executeDataDeletionRestore(ctx, client, opts)
default:
opErr = fmt.Errorf("unsupported DR_SCENARIO '%s'", opts.Scenario)
}

if opErr != nil {
log.Fatalf("Scenario failed: %v", opErr)
}

fmt.Println("\n=== Summary ===")
fmt.Println(summary)
fmt.Println("Disaster recovery procedure completed.")
}

// executeDataDeletionRestore initiates a restore job for a specified snapshot in a MongoDB Atlas cluster.
func executeDataDeletionRestore(ctx context.Context, client *admin.APIClient, o recovery.DrOptions) (string, error) {
job := admin.DiskBackupSnapshotRestoreJob{SnapshotId: &o.SnapshotID, TargetClusterName: &o.ClusterName}
if o.DryRun {
return fmt.Sprintf("(dry-run) Would submit restore job for snapshot %s", o.SnapshotID), nil
}
_, _, err := client.CloudBackupsApi.CreateBackupRestoreJob(ctx, o.ProjectID, o.ClusterName, &job).Execute()
if err != nil {
return "", fmt.Errorf("create restore job: %w", err)
}
return fmt.Sprintf("Restore job submitted for snapshot %s", o.SnapshotID), nil
}

// simulateRegionalOutage modifies the electable node count in a target region for a MongoDB Atlas cluster.
func simulateRegionalOutage(ctx context.Context, client *admin.APIClient, o recovery.DrOptions) (string, error) {
cluster, _, err := client.ClustersApi.GetCluster(ctx, o.ProjectID, o.ClusterName).Execute()
if err != nil {
return "", fmt.Errorf("get cluster: %w", err)
}
if !cluster.HasReplicationSpecs() {
return "", fmt.Errorf("cluster has no replication specs")
}
repl := cluster.GetReplicationSpecs()
addedNodes, foundTarget := recovery.AddElectableNodesToRegion(repl, o.TargetRegion, o.AddNodes)
if !foundTarget {
return "", fmt.Errorf("target region '%s' not found in replication specs", o.TargetRegion)
}
zeroedRegions := 0
if o.OutageRegion != "" {
zeroedRegions = recovery.ZeroElectableNodesInRegion(repl, o.OutageRegion)
}
payload := admin.NewClusterDescription20240805()
payload.SetReplicationSpecs(repl)
if o.DryRun {
return fmt.Sprintf("(dry-run) Would add %d electable nodes to %s%s", addedNodes, o.TargetRegion, typeutils.SuffixZeroed(zeroedRegions, o.OutageRegion)), nil
}
_, _, err = client.ClustersApi.UpdateCluster(ctx, o.ProjectID, o.ClusterName, payload).Execute()
if err != nil {
return "", fmt.Errorf("update cluster: %w", err)
}
return fmt.Sprintf("Added %d electable nodes to %s%s", addedNodes, o.TargetRegion, typeutils.SuffixZeroed(zeroedRegions, o.OutageRegion)), nil
}

Loading
Loading