diff --git a/docs/command/atlas-backups-restores-start.txt b/docs/command/atlas-backups-restores-start.txt index f153630589..5013a092c7 100644 --- a/docs/command/atlas-backups-restores-start.txt +++ b/docs/command/atlas-backups-restores-start.txt @@ -17,7 +17,8 @@ Start a restore job for your project and cluster. If you create an automated or pointInTime restore job, Atlas removes all existing data on the target cluster prior to the restore. To use this command, you must authenticate with a user account or an API key with the Project Owner role. -Atlas supports this command only for M10+ clusters. +Atlas supports this command only for Flex and M10+ clusters. +Flex clusters support only automated restore jobs. Syntax ------ @@ -137,6 +138,17 @@ Examples --targetProjectId 1a2345b67c8e9a12f3456de7 +.. code-block:: + :copyable: false + + # Create an automated restore for a Flex Cluster: + atlas backup restore start automated \ + --clusterName myFlexSource \ + --snapshotId 5e7e00128f8ce03996a47179 \ + --targetClusterName myFlexCluster \ + --targetProjectId 1a2345b67c8e9a12f3456de7 + + .. code-block:: :copyable: false diff --git a/internal/cli/backup/restores/start.go b/internal/cli/backup/restores/start.go index fefea4c816..e82465c067 100644 --- a/internal/cli/backup/restores/start.go +++ b/internal/cli/backup/restores/start.go @@ -60,14 +60,37 @@ func (opts *StartOpts) initStore(ctx context.Context) func() error { var startTemplate = "Restore job '{{.Id}}' successfully started\n" func (opts *StartOpts) Run() error { + r, err := opts.store.CreateRestoreFlexClusterJobs(opts.ConfigProjectID(), opts.clusterName, opts.newFlexBackupRestoreJobCreate()) + if err == nil { + return opts.Print(r) + } + + apiError, ok := admin.AsError(err) + if !ok { + return commonerrors.Check(err) + } + + if apiError.ErrorCode != cannotUseNotFlexWithFlexApisErrorCode { + return commonerrors.Check(err) + } + request := opts.newCloudProviderSnapshotRestoreJob() - r, err := opts.store.CreateRestoreJobs(opts.ConfigProjectID(), opts.clusterName, request) + restoreJob, err := opts.store.CreateRestoreJobs(opts.ConfigProjectID(), opts.clusterName, request) if err != nil { return commonerrors.Check(err) } - return opts.Print(r) + return opts.Print(restoreJob) +} + +func (opts *StartOpts) newFlexBackupRestoreJobCreate() *admin.FlexBackupRestoreJobCreate20241113 { + return &admin.FlexBackupRestoreJobCreate20241113{ + SnapshotId: opts.snapshotID, + TargetDeploymentItemName: opts.targetClusterName, + TargetProjectId: &opts.targetProjectID, + InstanceName: &opts.clusterName, + } } func (opts *StartOpts) newCloudProviderSnapshotRestoreJob() *admin.DiskBackupSnapshotRestoreJob { @@ -134,6 +157,7 @@ func markRequiredPointInTimeRestoreFlags(cmd *cobra.Command) error { return cmd.MarkFlagRequired(flag.TargetClusterName) } +// StartBuilder builds a cobra.Command that can run as: // atlas backup(s) restore(s) job(s) start . func StartBuilder() *cobra.Command { opts := new(StartOpts) @@ -142,7 +166,8 @@ func StartBuilder() *cobra.Command { Short: "Start a restore job for your project and cluster.", Long: `If you create an automated or pointInTime restore job, Atlas removes all existing data on the target cluster prior to the restore. -` + fmt.Sprintf("%s\n%s", fmt.Sprintf(usage.RequiredRole, "Project Owner"), "Atlas supports this command only for M10+ clusters."), +` + fmt.Sprintf("%s\n%s\n%s", fmt.Sprintf(usage.RequiredRole, "Project Owner"), "Atlas supports this command only for Flex and M10+ clusters.", + "Flex clusters support only automated restore jobs."), Args: require.ExactValidArgs(1), ValidArgs: []string{automatedRestore, downloadRestore, pointInTimeRestore}, Annotations: map[string]string{ @@ -156,6 +181,13 @@ func StartBuilder() *cobra.Command { --targetClusterName myDemo2 \ --targetProjectId 1a2345b67c8e9a12f3456de7 + # Create an automated restore for a Flex Cluster: + atlas backup restore start automated \ + --clusterName myFlexSource \ + --snapshotId 5e7e00128f8ce03996a47179 \ + --targetClusterName myFlexCluster \ + --targetProjectId 1a2345b67c8e9a12f3456de7 + # Create a point-in-time restore: atlas backup restore start pointInTime \ --clusterName myDemo \ diff --git a/internal/cli/backup/restores/start_test.go b/internal/cli/backup/restores/start_test.go index 1ffa3a9ec2..e6f6188a2f 100644 --- a/internal/cli/backup/restores/start_test.go +++ b/internal/cli/backup/restores/start_test.go @@ -21,6 +21,7 @@ import ( "github.com/golang/mock/gomock" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mocks" + "github.com/stretchr/testify/require" atlasv2 "go.mongodb.org/atlas-sdk/v20241113004/admin" ) @@ -39,15 +40,44 @@ func TestStart_Run(t *testing.T) { targetProjectID: "1", } + expectedError := &atlasv2.GenericOpenAPIError{} + expectedError.SetModel(atlasv2.ApiError{ErrorCode: cannotUseNotFlexWithFlexApisErrorCode}) + + mockStore. + EXPECT(). + CreateRestoreFlexClusterJobs(listOpts.ProjectID, "Cluster0", listOpts.newFlexBackupRestoreJobCreate()). + Return(nil, expectedError). + Times(1) + mockStore. EXPECT(). CreateRestoreJobs(listOpts.ProjectID, "Cluster0", listOpts.newCloudProviderSnapshotRestoreJob()). Return(expected, nil). Times(1) - if err := listOpts.Run(); err != nil { - t.Fatalf("Run() unexpected error: %v", err) + require.NoError(t, listOpts.Run()) + }) + + t.Run("Flex Cluster automated restore job", func(t *testing.T) { + listOpts := &StartOpts{ + store: mockStore, + method: automatedRestore, + clusterName: "Cluster0", + targetClusterName: "Cluster1", + targetProjectID: "1", } + + expectedFlex := &atlasv2.FlexBackupRestoreJob20241113{} + expectedError := &atlasv2.GenericOpenAPIError{} + expectedError.SetModel(atlasv2.ApiError{ErrorCode: cannotUseNotFlexWithFlexApisErrorCode}) + + mockStore. + EXPECT(). + CreateRestoreFlexClusterJobs(listOpts.ProjectID, "Cluster0", listOpts.newFlexBackupRestoreJobCreate()). + Return(expectedFlex, nil). + Times(1) + + require.NoError(t, listOpts.Run()) }) t.Run(pointInTimeRestore, func(t *testing.T) { @@ -59,15 +89,22 @@ func TestStart_Run(t *testing.T) { targetProjectID: "1", } + expectedError := &atlasv2.GenericOpenAPIError{} + expectedError.SetModel(atlasv2.ApiError{ErrorCode: cannotUseNotFlexWithFlexApisErrorCode}) + + mockStore. + EXPECT(). + CreateRestoreFlexClusterJobs(listOpts.ProjectID, "Cluster0", listOpts.newFlexBackupRestoreJobCreate()). + Return(nil, expectedError). + Times(1) + mockStore. EXPECT(). CreateRestoreJobs(listOpts.ProjectID, "Cluster0", listOpts.newCloudProviderSnapshotRestoreJob()). Return(expected, nil). Times(1) - if err := listOpts.Run(); err != nil { - t.Fatalf("Run() unexpected error: %v", err) - } + require.NoError(t, listOpts.Run()) }) t.Run(downloadRestore, func(t *testing.T) { @@ -77,14 +114,21 @@ func TestStart_Run(t *testing.T) { clusterName: "Cluster0", } + expectedError := &atlasv2.GenericOpenAPIError{} + expectedError.SetModel(atlasv2.ApiError{ErrorCode: cannotUseNotFlexWithFlexApisErrorCode}) + + mockStore. + EXPECT(). + CreateRestoreFlexClusterJobs(listOpts.ProjectID, "Cluster0", listOpts.newFlexBackupRestoreJobCreate()). + Return(nil, expectedError). + Times(1) + mockStore. EXPECT(). CreateRestoreJobs(listOpts.ProjectID, "Cluster0", listOpts.newCloudProviderSnapshotRestoreJob()). Return(expected, nil). Times(1) - if err := listOpts.Run(); err != nil { - t.Fatalf("Run() unexpected error: %v", err) - } + require.NoError(t, listOpts.Run()) }) }