Evitar a exclusão acidental do banco de dados

Nesta página, descrevemos como proteger os bancos de dados do Spanner contra exclusão acidental.

A proteção contra exclusão de banco de dados do Spanner impede a exclusão acidental de bancos de dados atuais por usuários ou contas de serviço com as permissões de IAM necessárias para excluir o banco de dados. Ao ativar a proteção contra exclusão de banco de dados, você protege os bancos de dados importantes para seus aplicativos e serviços. Use a proteção contra exclusão de banco de dados com recuperação pontual e recursos de backup para fornecer um conjunto abrangente de recursos de proteção de dados para seus bancos de dados do Spanner.

Por padrão, a configuração de proteção contra exclusão é desativada quando você cria o novo banco de dados. É possível ativar a configuração de proteção contra exclusão após a criação do banco de dados. Além disso, é possível ativar essa configuração em um banco de dados atual. Para proteger vários bancos de dados, ative a configuração em cada banco de dados individualmente. Ativar ou desativar a proteção contra exclusão não afeta o desempenho no banco de dados. Se você precisar excluir um banco de dados com a proteção ativada, será necessário desativar a proteção antes de excluí-lo.

Limitações

Não é possível ativar a proteção contra exclusão de bancos de dados nos seguintes cenários:

  • Se o banco de dados está sendo excluído.
  • Se o banco de dados está sendo restaurado a partir de um backup. Após a conclusão da operação de restauração, ative a proteção do banco de dados.

Além disso, os backups de um banco de dados e de bancos de dados restaurados de um backup não herdam a configuração de proteção contra exclusão do banco de dados de origem. Depois de restaurar um banco de dados de um backup, é necessário ativar separadamente a proteção contra exclusão do banco de dados.

Se você excluir o projeto, a proteção contra exclusão do banco de dados do Spanner não impedirá a exclusão do banco de dados ou da instância. Para saber mais sobre o que acontece quando você exclui o projeto, consulte Como encerrar (excluir) projetos.

Controle de acesso com o IAM

Para ativar a configuração de proteção contra exclusão do seu banco de dados, é necessário ter determinadas permissões do IAM.

Você precisa ter a permissão spanner.databases.update para ativar ou desativar a proteção contra exclusão do banco de dados. Se você só precisa ver o status da configuração do banco de dados, precisa ter a permissão spanner.databases.list ou spanner.databases.get. Para mais informações sobre como conceder permissões do IAM do Spanner, consulte Aplicar permissões do IAM.

Se você tiver o papel predefinido Administrador do banco de dados do Spannerroles/spanner.databaseAdmin no banco de dados, poderá atualizar e ativar a proteção contra exclusão dele.

É possível ativar a configuração de proteção contra exclusão de banco de dados em um banco de dados atual para evitar a exclusão acidental.

Ativar a proteção contra exclusão do banco de dados

É possível ativar a proteção contra exclusão do banco de dados usando a CLI gcloud, as bibliotecas de cliente e as APIs REST ou RPC. Não é possível ativar a proteção contra exclusão de bancos de dados usando o console do Google Cloud.

gcloud

Para ativar a configuração de proteção contra exclusão de um banco de dados, execute o seguinte comando:

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

As seguintes opções são obrigatórias:

DATABASE_ID
ID do banco de dados.
INSTANCE_ID
ID da instância para o banco de dados.

As seguintes opções são opcionais:

--async
Retorna imediatamente, sem aguardar a conclusão da operação em andamento.

Bibliotecas de cliente

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

Verificar se a proteção contra exclusão está ativada em um banco de dados

É possível determinar se a proteção contra exclusão do banco de dados está ativada visualizando a configuração dele.

gcloud

Para verificar se um banco de dados está com a proteção contra exclusão ativada, execute o comando gcloud spanner databases describe para receber informações detalhadas sobre um banco de dados ou execute gcloud spanner databases list para ver informações detalhadas sobre os bancos de dados em uma instância.

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

As seguintes opções são obrigatórias:

PROJECT_ID
ID do projeto no banco de dados.
INSTANCE_ID
ID da instância para o banco de dados.
DATABASE_ID
ID do banco de dados.

Se a proteção contra exclusão estiver ativada, um parâmetro enableDropProtection: true vai aparecer na saída.

Desativar a proteção contra exclusão do banco de dados

É possível desativar a proteção contra exclusão de bancos de dados se um banco de dados não precisar mais dessa proteção ou se você precisar excluir um banco de dados que tenha essa configuração ativada.

Se você quiser excluir uma instância que tenha um ou mais bancos de dados com a proteção contra exclusão ativada, primeiro desative essa proteção em todos os bancos de dados dessa instância antes de excluí-la.

gcloud

Para desativar a configuração de proteção contra exclusão de um banco de dados, execute o seguinte comando:

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

As seguintes opções são obrigatórias:

DATABASE_ID
ID do banco de dados.
INSTANCE_ID
ID da instância para o banco de dados.

As seguintes opções são opcionais:

--async
Retorna imediatamente, sem aguardar a conclusão da operação em andamento.

A seguir