Mencegah penghapusan database secara tidak sengaja

Halaman ini menjelaskan cara melindungi database Spanner dari penghapusan yang tidak disengaja.

Perlindungan penghapusan database Spanner mencegah penghapusan database yang ada secara tidak sengaja oleh pengguna atau akun layanan yang memiliki izin IAM yang diperlukan untuk menghapus database. Dengan mengaktifkan perlindungan penghapusan database, Anda dapat mengamankan database yang penting untuk aplikasi dan layanan Anda. Gunakan perlindungan penghapusan database bersama dengan pemulihan point-in-time dan fitur pencadangan untuk menyediakan kumpulan kemampuan perlindungan data yang komprehensif untuk database Spanner Anda.

Secara default, setelan perlindungan penghapusan dinonaktifkan saat Anda membuat database baru. Anda dapat mengaktifkan setelan perlindungan penghapusan setelah pembuatan database berhasil. Selain itu, Anda dapat mengaktifkan setelan ini di database yang ada. Jika Anda ingin melindungi beberapa database, aktifkan setelan di setiap database satu per satu. Mengaktifkan atau menonaktifkan perlindungan penghapusan tidak memengaruhi performa database. Jika perlu menghapus database yang mengaktifkan perlindungan database, Anda harus menonaktifkan perlindungan sebelum dapat menghapus database.

Batasan

Anda tidak dapat mengaktifkan perlindungan penghapusan database dalam skenario berikut:

  • Jika database sedang dihapus.
  • Jika database sedang dipulihkan dari cadangan. (Setelah operasi pemulihan selesai, Anda dapat mengaktifkan perlindungan database).

Selain itu, cadangan database dan database yang dipulihkan dari cadangan tidak mewarisi setelan perlindungan penghapusan database dari database sumbernya. Setelah memulihkan database dari cadangan, Anda harus mengaktifkan perlindungan penghapusan database secara terpisah.

Jika Anda menghapus project, perlindungan penghapusan database Spanner tidak mencegah penghapusan database atau instance Anda. Untuk mengetahui informasi selengkapnya tentang hal yang terjadi saat Anda menghapus project, lihat Menghentikan (menghapus) project.

Kontrol akses dengan IAM

Untuk mengaktifkan setelan perlindungan penghapusan database, Anda harus memiliki izin IAM tertentu.

Anda harus memiliki izin spanner.databases.update untuk mengaktifkan atau menonaktifkan perlindungan penghapusan database. Jika hanya perlu melihat status konfigurasi database, Anda harus memiliki izin spanner.databases.list atau spanner.databases.get. Untuk mengetahui informasi tentang cara memberikan izin IAM Spanner, lihat Menerapkan izin IAM.

Jika memiliki peran Admin Database Spanner roles/spanner.databaseAdmin yang telah ditentukan sebelumnya untuk database, Anda dapat memperbarui dan mengaktifkan perlindungan penghapusan database.

Anda dapat mengaktifkan setelan perlindungan penghapusan database di database yang ada untuk mencegah penghapusan database secara tidak sengaja.

Mengaktifkan perlindungan penghapusan database

Anda dapat mengaktifkan perlindungan penghapusan database menggunakan gcloud CLI, library klien, dan API REST atau RPC. Anda tidak dapat mengaktifkan perlindungan penghapusan database menggunakan konsol Google Cloud.

gcloud

Untuk mengaktifkan setelan perlindungan penghapusan database, jalankan perintah berikut:

  gcloud spanner databases update
  DATABASE_ID --instance=INSTANCE_ID
  --enable-drop-protection [--async]

Opsi berikut diperlukan:

DATABASE_ID
ID database.
INSTANCE_ID
ID instance untuk database.

Opsi berikut bersifat opsional:

--async
Segera kembali, tanpa menunggu operasi yang sedang berlangsung selesai.

Library klien

C++

void UpdateDatabase(google::cloud::spanner_admin::DatabaseAdminClient client,
                    std::string const& project_id,
                    std::string const& instance_id,
                    std::string const& database_id, bool drop_protection) {
  google::cloud::spanner::Database db(project_id, instance_id, database_id);
  google::spanner::admin::database::v1::Database database;
  database.set_name(db.FullName());
  database.set_enable_drop_protection(drop_protection);
  google::protobuf::FieldMask update_mask;
  update_mask.add_paths("enable_drop_protection");
  auto updated = client.UpdateDatabase(database, update_mask).get();
  if (!updated) throw std::move(updated).status();
  std::cout << "Database " << updated->name() << " successfully updated.\n";
}

C#


using Google.Cloud.Spanner.Admin.Database.V1;
using Google.Cloud.Spanner.Common.V1;
using Google.Protobuf.WellKnownTypes;
using System;
using System.Threading.Tasks;

public class UpdateDatabaseAsyncSample
{
    public async Task<Database> UpdateDatabaseAsync(string projectId, string instanceId, string databaseId)
    {
        var databaseAdminClient = await DatabaseAdminClient.CreateAsync();
        var databaseName = DatabaseName.Format(projectId, instanceId, databaseId);
        Database databaseToUpdate = await databaseAdminClient.GetDatabaseAsync(databaseName);

        databaseToUpdate.EnableDropProtection = true;
        var updateDatabaseRequest = new UpdateDatabaseRequest()
        {
            Database = databaseToUpdate,
            UpdateMask = new FieldMask { Paths = { "enable_drop_protection" } }
        };

        var operation = await databaseAdminClient.UpdateDatabaseAsync(updateDatabaseRequest);

        // Wait until the operation has finished.
        Console.WriteLine("Waiting for the operation to finish.");
        var completedResponse = await operation.PollUntilCompletedAsync();

        if (completedResponse.IsFaulted)
        {
            Console.WriteLine($"Error while updating database {databaseId}: {completedResponse.Exception}");
            throw completedResponse.Exception;
        }

        Console.WriteLine($"Updated database {databaseId}.");

        // Return the updated database.
        return completedResponse.Result;
    }
}

Go

import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
	"google.golang.org/genproto/protobuf/field_mask"
)

func updateDatabase(ctx context.Context, w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	// Instantiate database admin client.
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return fmt.Errorf("updateDatabase.NewDatabaseAdminClient: %w", err)
	}
	defer adminClient.Close()

	// Instantiate the request for performing update database operation.
	op, err := adminClient.UpdateDatabase(ctx, &adminpb.UpdateDatabaseRequest{
		Database: &adminpb.Database{
			Name:                 db,
			EnableDropProtection: true,
		},
		UpdateMask: &field_mask.FieldMask{
			Paths: []string{"enable_drop_protection"},
		},
	})
	if err != nil {
		return fmt.Errorf("updateDatabase.UpdateDatabase: %w", err)
	}

	// Wait for update database operation to complete.
	fmt.Fprintf(w, "Waiting for update database operation to complete [%s]\n", db)
	if _, err := op.Wait(ctx); err != nil {
		return fmt.Errorf("updateDatabase.Wait: %w", err)
	}
	fmt.Fprintf(w, "Updated database [%s]\n", db)
	return nil
}

Java


import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
import com.google.common.collect.Lists;
import com.google.protobuf.FieldMask;
import com.google.spanner.admin.database.v1.Database;
import com.google.spanner.admin.database.v1.DatabaseName;
import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata;
import com.google.spanner.admin.database.v1.UpdateDatabaseRequest;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class UpdateDatabaseSample {

  static void updateDatabase() {
    // TODO(developer): Replace these variables before running the sample.
    final String projectId = "my-project";
    final String instanceId = "my-instance";
    final String databaseId = "my-database";

    updateDatabase(projectId, instanceId, databaseId);
  }

  static void updateDatabase(
      String projectId, String instanceId, String databaseId) {
    try (Spanner spanner =
        SpannerOptions.newBuilder().setProjectId(projectId).build().getService();
        DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) {
      final Database database =
          Database.newBuilder()
              .setName(DatabaseName.of(projectId, instanceId, databaseId).toString())
              .setEnableDropProtection(true).build();
      final UpdateDatabaseRequest updateDatabaseRequest =
          UpdateDatabaseRequest.newBuilder()
              .setDatabase(database)
              .setUpdateMask(
                  FieldMask.newBuilder().addAllPaths(
                      Lists.newArrayList("enable_drop_protection")).build())
              .build();
      OperationFuture<Database, UpdateDatabaseMetadata> operation =
          databaseAdminClient.updateDatabaseAsync(updateDatabaseRequest);
      System.out.printf("Waiting for update operation for %s to complete...\n", databaseId);
      Database updatedDb = operation.get(5, TimeUnit.MINUTES);
      System.out.printf("Updated database %s.\n", updatedDb.getName());
    } catch (ExecutionException | TimeoutException e) {
      // If the operation failed during execution, expose the cause.
      throw SpannerExceptionFactory.asSpannerException(e.getCause());
    } catch (InterruptedException e) {
      // Throw when a thread is waiting, sleeping, or otherwise occupied,
      // and the thread is interrupted, either before or during the activity.
      throw SpannerExceptionFactory.propagateInterrupt(e);
    }
  }
}

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const projectId = 'my-project-id';

// Imports the Google Cloud client library
const {Spanner, protos} = require('@google-cloud/spanner');

// creates a client
const spanner = new Spanner({
  projectId: projectId,
});

const databaseAdminClient = spanner.getDatabaseAdminClient();

async function updateDatabase() {
  // Update the database metadata fields
  try {
    console.log(
      `Updating database ${databaseAdminClient.databasePath(
        projectId,
        instanceId,
        databaseId
      )}.`
    );
    const [operation] = await databaseAdminClient.updateDatabase({
      database: {
        name: databaseAdminClient.databasePath(
          projectId,
          instanceId,
          databaseId
        ),
        enableDropProtection: true,
      },
      // updateMask contains the fields to be updated in database
      updateMask: (protos.google.protobuf.FieldMask = {
        paths: ['enable_drop_protection'],
      }),
    });
    console.log(
      `Waiting for update operation for ${databaseId} to complete...`
    );
    await operation.promise();
    console.log(`Updated database ${databaseId}.`);
  } catch (err) {
    console.log('ERROR:', err);
  }
}
updateDatabase();

PHP

use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient;
use Google\Cloud\Spanner\Admin\Database\V1\Database;
use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseRequest;
use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseRequest;
use Google\Protobuf\FieldMask;

/**
 * Updates the drop protection setting for a database.
 * Example:
 * ```
 * update_database($projectId, $instanceId, $databaseId);
 * ```
 *
 * @param string $projectId The Google Cloud project ID.
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function update_database(string $projectId, string $instanceId, string $databaseId): void
{
    $newUpdateMaskField = new FieldMask([
        'paths' => ['enable_drop_protection']
    ]);
    $databaseAdminClient = new DatabaseAdminClient();
    $databaseFullName = DatabaseAdminClient::databaseName($projectId, $instanceId, $databaseId);
    $database = (new Database())
        ->setEnableDropProtection(true)
        ->setName($databaseFullName);

    printf('Updating database %s', $databaseId);
    $operation = $databaseAdminClient->updateDatabase((new UpdateDatabaseRequest())
        ->setDatabase($database)
        ->setUpdateMask($newUpdateMaskField));

    $operation->pollUntilComplete();

    $database = $databaseAdminClient->getDatabase(
        new GetDatabaseRequest(['name' => $databaseFullName])
    );
    printf(
        'Updated the drop protection for %s to %s' . PHP_EOL,
        $database->getName(),
        $database->getEnableDropProtection()
    );
}

Python

def update_database(instance_id, database_id):
    """Updates the drop protection setting for a database."""
    from google.cloud.spanner_admin_database_v1.types import spanner_database_admin

    spanner_client = spanner.Client()
    database_admin_api = spanner_client.database_admin_api

    request = spanner_database_admin.UpdateDatabaseRequest(
        database=spanner_database_admin.Database(
            name=database_admin_api.database_path(
                spanner_client.project, instance_id, database_id
            ),
            enable_drop_protection=True,
        ),
        update_mask={"paths": ["enable_drop_protection"]},
    )
    operation = database_admin_api.update_database(request=request)
    print(
        "Waiting for update operation for {}/databases/{} to complete...".format(
            database_admin_api.instance_path(spanner_client.project, instance_id),
            database_id,
        )
    )
    operation.result(OPERATION_TIMEOUT_SECONDS)

    print(
        "Updated database {}/databases/{}.".format(
            database_admin_api.instance_path(spanner_client.project, instance_id),
            database_id,
        )
    )

Ruby

require "google/cloud/spanner/admin/database"

##
# This is a snippet for showcasing how to update database.
#
# @param project_id  [String] The ID of the Google Cloud project.
# @param instance_id [String] The ID of the spanner instance.
# @param database_id [String] The ID of the database.
#
def spanner_update_database project_id:, instance_id:, database_id:
  client = Google::Cloud::Spanner::Admin::Database.database_admin project_id: project_id
  db_path = client.database_path project: project_id, instance: instance_id, database: database_id
  database = client.get_database name: db_path

  puts "Updating database #{database.name}"
  database.enable_drop_protection = true
  job = client.update_database database: database, update_mask: { paths: ["enable_drop_protection"] }
  puts "Waiting for update operation for #{database.name} to complete..."
  job.wait_until_done!
  puts "Updated database #{database.name}"
end

Memeriksa apakah database mengaktifkan perlindungan penghapusan

Anda dapat menentukan apakah database Anda mengaktifkan perlindungan penghapusan dengan melihat konfigurasi database.

gcloud

Untuk memeriksa apakah database telah mengaktifkan perlindungan penghapusan, Anda dapat menjalankan perintah gcloud spanner databases describe untuk mendapatkan informasi mendetail tentang database, atau Anda dapat menjalankan gcloud spanner databases list untuk mendapatkan informasi mendetail tentang database dalam instance.

  gcloud spanner databases describe
  projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID

Opsi berikut diperlukan:

PROJECT_ID
ID project untuk database.
INSTANCE_ID
ID instance untuk database.
DATABASE_ID
ID database.

Jika perlindungan penghapusan diaktifkan, Anda akan melihat parameter enableDropProtection: true dalam output.

Menonaktifkan perlindungan penghapusan database

Anda dapat menonaktifkan perlindungan penghapusan database jika database tidak lagi memerlukan perlindungan ini atau jika Anda perlu menghapus database yang mengaktifkan setelan ini.

Jika ingin menghapus instance yang memiliki satu atau beberapa database dengan perlindungan penghapusan diaktifkan, Anda harus menonaktifkan perlindungan penghapusan di semua database dalam instance tersebut terlebih dahulu sebelum dapat menghapus instance.

gcloud

Untuk menonaktifkan setelan perlindungan penghapusan database, jalankan perintah berikut:

  gcloud spanner databases update
  DATABASE_ID --instance=INSTANCE_ID
  --no-enable-drop-protection [--async]

Opsi berikut diperlukan:

DATABASE_ID
ID database.
INSTANCE_ID
ID instance untuk database.

Opsi berikut bersifat opsional:

--async
Segera kembali, tanpa menunggu operasi yang sedang berlangsung selesai.

Langkah selanjutnya