Duplicate a disk with clones


This document provides information about how disk clones work and how to create a disk clone. Disk cloning lets you make instantly usable duplicates of existing disks. Create a disk clone in scenarios where you want to create an identical copy of an existing disk that you can instantly attach to a VM, such as the following:

  • Creating staging environments by duplicating production data to debug without disturbing production
  • Creating copies for database backup verification
  • Moving non-boot disk data to a new project
  • Duplicating disks while scaling out your VMs

To protect against disaster recovery, back up your disk with standard snapshots instead of using disk clones. To capture disk contents at regular intervals without creating new disks, use instant snapshots because they're more storage-efficient than clones. For additional disk protection options, see Data protection options.

Before you begin

  • If you haven't already, set up authentication. Authentication is the process by which your identity is verified for access to Google Cloud services and APIs. To run code or samples from a local development environment, you can authenticate to Compute Engine as follows.

    Select the tab for how you plan to use the samples on this page:

    Console

    When you use the Google Cloud console to access Google Cloud services and APIs, you don't need to set up authentication.

    gcloud

    1. Install the Google Cloud CLI, then initialize it by running the following command:

      gcloud init
    2. Set a default region and zone.

    Terraform

    To use the Terraform samples on this page in a local development environment, install and initialize the gcloud CLI, and then set up Application Default Credentials with your user credentials.

    1. Install the Google Cloud CLI.
    2. To initialize the gcloud CLI, run the following command:

      gcloud init
    3. Create local authentication credentials for your Google Account:

      gcloud auth application-default login

    For more information, see Set up authentication for a local development environment.

    Go

    To use the Go samples on this page in a local development environment, install and initialize the gcloud CLI, and then set up Application Default Credentials with your user credentials.

    1. Install the Google Cloud CLI.
    2. To initialize the gcloud CLI, run the following command:

      gcloud init
    3. Create local authentication credentials for your Google Account:

      gcloud auth application-default login

    For more information, see Set up authentication for a local development environment.

    Java

    To use the Java samples on this page in a local development environment, install and initialize the gcloud CLI, and then set up Application Default Credentials with your user credentials.

    1. Install the Google Cloud CLI.
    2. To initialize the gcloud CLI, run the following command:

      gcloud init
    3. Create local authentication credentials for your Google Account:

      gcloud auth application-default login

    For more information, see Set up authentication for a local development environment.

    Python

    To use the Python samples on this page in a local development environment, install and initialize the gcloud CLI, and then set up Application Default Credentials with your user credentials.

    1. Install the Google Cloud CLI.
    2. To initialize the gcloud CLI, run the following command:

      gcloud init
    3. Create local authentication credentials for your Google Account:

      gcloud auth application-default login

    For more information, see Set up authentication for a local development environment.

    REST

    To use the REST API samples on this page in a local development environment, you use the credentials you provide to the gcloud CLI.

      Install the Google Cloud CLI, then initialize it by running the following command:

      gcloud init

    For more information, see Authenticate for using REST in the Google Cloud authentication documentation.

How disk cloning works

When you clone a disk, you create a new disk that contains all the data on the source disk. You can create a disk clone even if the existing disk is attached to a VM instance.

The disk type of the clone must be the same as that of the source disk. However, you can modify properties on the clone, such as the disk size. You can also delete the source disk without any risk of deleting the clone.

Supported disk types

You can create disk clones only for the following disk types:

  • Persistent Disk: All types of Persistent Disk
  • Google Cloud Hyperdisk: Hyperdisk Balanced, Hyperdisk Extreme, and Hyperdisk Throughput

Restrictions

Depending on the type of disk, disk clones have the following restrictions:

General restrictions

The following restrictions apply to clones of all disk types:

  • The disk type of the clone must be the same as that of the source disk.
  • You cannot create a zonal disk clone of an existing zonal disk in a different zone.
  • The size of the clone must be at least the size of the source disk. If you create a clone using the Google Cloud console, then you cannot specify a disk size and the clone will be the same size as the source disk.
  • If you use a customer-supplied encryption key or a customer-managed encryption key to encrypt the source disk, you must use the same key to encrypt the clone. For more information, see Creating a clone of an encrypted source disk.
  • You can't delete the source disk while its clone is being created.
  • The compute instance that the source disk is attached to won't be able to power on while the clone is being created.
  • If the source disk was marked to be deleted along with the VM that it is attached to, then you can't delete the VM while the clone is being created.
  • You can create at most one clone of a given source disk or its clones every 30 seconds.
  • You can have at most 1000 simultaneous disk clones of a given source disk or its clones. Exceeding this limit returns an internalError. However, if you create a disk clone and delete it later, then the deleted disk clone is not included in this limit.
  • After a disk is cloned, any subsequent clones of that disk or of its clones are counted against the limit of 1000 simultaneous disk clones for the original source disk and are counted against the limit of creating at most one clone every 30 seconds.
  • The amount of space that you can use when cloning disks is limited. You can create at most 1 TB of cloned disk every 15 minutes, with a burst request limit of 257 TB.

Restrictions for Persistent Disk clones

Disk clones for Persistent Disk have the following restrictions:

  • You cannot create a zonal disk clone from a regional disk.
  • To create a regional disk clone from a zonal source disk, one of the replica zones of the regional disk clone must match the zone of the source disk.
  • After creation, a regional disk clone is usable within 3 minutes, on average. However, the disk might take tens of minutes to become fully replicated and reach a state where the recovery point objective (RPO) is near zero.

Restrictions for Google Cloud Hyperdisk clones

You can create disk clones only for Hyperdisk Balanced, Hyperdisk Extreme, and Hyperdisk Throughput volumes.

Error messages

If you exceed the cloning frequency limits, the request fails with the following error:

RATE LIMIT: ERROR: (gcloud.compute.disks.create) Could not fetch resource:
 - Operation rate exceeded for resource RESOURCE. Too frequent operations from the source resource.

Create disk clones

This section explains how you can duplicate an existing disk and create a disk clone.

For detailed steps, depending on the type of disk clone creation, see one of the following sections in this document:

Create a zonal disk clone

You can create zonal disk clones of an existing disk in the same zone as the source disk by using the Google Cloud console, the Google Cloud CLI, or REST.

Console

  1. In the Google Cloud console, go to the Disks page.

    Go to Disks

  2. In the list of disks, navigate to the disk that you want to clone.

  3. In the Actions column, click the menu button and select Clone disk.

    Create clone.

    In the Clone disk panel that appears, do the following:

    1. In the Name field, specify a name for the cloned disk.
    2. For Location, verify that Single zone is selected.
    3. Under Properties, review other details for the cloned disk.
    4. To finish creating the cloned disk, click Create.

gcloud

To clone a zonal source disk and create a new zonal disk, run the disks create command and specify the --source-disk flag:

gcloud compute disks create TARGET_DISK_NAME \
    --description="cloned disk" \
    --source-disk=projects/PROJECT_ID/zones/ZONE/disks/SOURCE_DISK_NAME

Replace the following:

  • TARGET_DISK_NAME: the name for the new disk.
  • PROJECT_ID: the project ID where you want to clone the disk.
  • ZONE: the zone of the source and new disk.
  • SOURCE_DISK_NAME: the name of the source disk.

Terraform

To create a disk clone, use the google_compute_disk resource.

resource "google_compute_disk" "default" {
  name  = "disk-name1"
  type  = "pd-ssd"
  zone  = "us-central1-a"
  image = "debian-11-bullseye-v20220719"
  labels = {
    environment = "dev"
  }
  physical_block_size_bytes = 4096
}

To learn how to apply or remove a Terraform configuration, see Basic Terraform commands.

Go

Go

Before trying this sample, follow the Go setup instructions in the Compute Engine quickstart using client libraries. For more information, see the Compute Engine Go API reference documentation.

To authenticate to Compute Engine, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

import (
	"context"
	"fmt"
	"io"

	compute "cloud.google.com/go/compute/apiv1"
	computepb "cloud.google.com/go/compute/apiv1/computepb"
	"google.golang.org/protobuf/proto"
)

// createDiskFromDisk creates a new disk with the contents of
// an already existitng disk. Type, and size and zone may differ.
func createDiskFromDisk(
	w io.Writer,
	projectID, zone, diskName, diskType, sourceDiskLink string,
	diskSizeGb int64,
) error {
	// projectID := "your_project_id"
	// zone := "us-west3-b" // should match diskType below
	// diskName := "your_disk_name"
	// diskType := "zones/us-west3-b/diskTypes/pd-ssd"
	// sourceDiskLink := "projects/your_project_id/global/disks/disk_name"
	// diskSizeGb := 120

	ctx := context.Background()
	disksClient, err := compute.NewDisksRESTClient(ctx)
	if err != nil {
		return fmt.Errorf("NewDisksRESTClient: %w", err)
	}
	defer disksClient.Close()

	req := &computepb.InsertDiskRequest{
		Project: projectID,
		Zone:    zone,
		DiskResource: &computepb.Disk{
			Name:       proto.String(diskName),
			Zone:       proto.String(zone),
			Type:       proto.String(diskType),
			SourceDisk: proto.String(sourceDiskLink),
			SizeGb:     proto.Int64(diskSizeGb),
		},
	}

	op, err := disksClient.Insert(ctx, req)
	if err != nil {
		return fmt.Errorf("unable to create disk: %w", err)
	}

	if err = op.Wait(ctx); err != nil {
		return fmt.Errorf("unable to wait for the operation: %w", err)
	}

	fmt.Fprintf(w, "Disk created\n")

	return nil
}

Java

Java

Before trying this sample, follow the Java setup instructions in the Compute Engine quickstart using client libraries. For more information, see the Compute Engine Java API reference documentation.

To authenticate to Compute Engine, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.


import com.google.cloud.compute.v1.Disk;
import com.google.cloud.compute.v1.DisksClient;
import com.google.cloud.compute.v1.Operation;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CreateFromSource {

  public static void main(String[] args)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // TODO(developer): Replace these variables before running the sample.
    // Project ID or project number of the Cloud project you want to use.
    String project = "YOUR_PROJECT_ID";

    // Name of the zone in which you want to create the disk.
    String zone = "europe-central2-b";

    // Name of the disk you want to create.
    String diskName = "YOUR_DISK_NAME";

    // The type of disk you want to create. This value uses the following format:
    // "zones/{zone}/diskTypes/(pd-standard|pd-ssd|pd-balanced|pd-extreme)".
    // For example: "zones/us-west3-b/diskTypes/pd-ssd"
    String diskType = String.format("zones/%s/diskTypes/pd-ssd", zone);

    // Size of the new disk in gigabytes.
    int diskSizeGb = 10;

    // A link to the disk you want to use as a source for the new disk.
    // This value uses the following format:
    // "projects/{project_name}/zones/{zone}/disks/{disk_name}"
    String diskLink = String.format("projects/%s/zones/%s/disks/%s", "PROJECT_NAME", "ZONE",
        "DISK_NAME");

    createDiskFromDisk(project, zone, diskName, diskType, diskSizeGb, diskLink);
  }

  // Creates a disk in a project in a given zone.
  public static void createDiskFromDisk(String project, String zone, String diskName,
      String diskType, int diskSizeGb, String diskLink)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // Initialize client that will be used to send requests. This client only needs to be created
    // once, and can be reused for multiple requests. After completing all of your requests, call
    // the `disksClient.close()` method on the client to safely
    // clean up any remaining background resources.
    try (DisksClient disksClient = DisksClient.create()) {

      // Create the disk.
      Disk disk = Disk.newBuilder()
          .setZone(zone)
          .setSizeGb(diskSizeGb)
          .setSourceDisk(diskLink)
          .setType(diskType)
          .setName(diskName)
          .build();

      // Wait for the insert instance operation to complete.
      Operation operation = disksClient.insertAsync(project, zone, disk)
          .get(3, TimeUnit.MINUTES);

      if (operation.hasError()) {
        System.out.println("Disk creation failed!");
        throw new Error(operation.getError().toString());
      }
      System.out.println(
          "Disk created from source. Operation Status: " + operation.getStatus());
    }
  }
}

Python

Python

Before trying this sample, follow the Python setup instructions in the Compute Engine quickstart using client libraries. For more information, see the Compute Engine Python API reference documentation.

To authenticate to Compute Engine, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

from __future__ import annotations

import sys
from typing import Any

from google.api_core.extended_operation import ExtendedOperation
from google.cloud import compute_v1


def wait_for_extended_operation(
    operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300
) -> Any:
    """
    Waits for the extended (long-running) operation to complete.

    If the operation is successful, it will return its result.
    If the operation ends with an error, an exception will be raised.
    If there were any warnings during the execution of the operation
    they will be printed to sys.stderr.

    Args:
        operation: a long-running operation you want to wait on.
        verbose_name: (optional) a more verbose name of the operation,
            used only during error and warning reporting.
        timeout: how long (in seconds) to wait for operation to finish.
            If None, wait indefinitely.

    Returns:
        Whatever the operation.result() returns.

    Raises:
        This method will raise the exception received from `operation.exception()`
        or RuntimeError if there is no exception set, but there is an `error_code`
        set for the `operation`.

        In case of an operation taking longer than `timeout` seconds to complete,
        a `concurrent.futures.TimeoutError` will be raised.
    """
    result = operation.result(timeout=timeout)

    if operation.error_code:
        print(
            f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}",
            file=sys.stderr,
            flush=True,
        )
        print(f"Operation ID: {operation.name}", file=sys.stderr, flush=True)
        raise operation.exception() or RuntimeError(operation.error_message)

    if operation.warnings:
        print(f"Warnings during {verbose_name}:\n", file=sys.stderr, flush=True)
        for warning in operation.warnings:
            print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True)

    return result


def create_disk_from_disk(
    project_id: str,
    zone: str,
    disk_name: str,
    disk_type: str,
    disk_size_gb: int,
    disk_link: str,
) -> compute_v1.Disk:
    """
    Creates a disk in a project in a given zone.

    Args:
        project_id: project ID or project number of the Cloud project you want to use.
        zone: name of the zone in which you want to create the disk.
        disk_name: name of the disk you want to create.
        disk_type: the type of disk you want to create. This value uses the following format:
            "zones/{zone}/diskTypes/(pd-standard|pd-ssd|pd-balanced|pd-extreme)".
            For example: "zones/us-west3-b/diskTypes/pd-ssd"
        disk_size_gb: size of the new disk in gigabytes
        disk_link: a link to the disk you want to use as a source for the new disk.
            This value uses the following format: "projects/{project_name}/zones/{zone}/disks/{disk_name}"

    Returns:
        An attachable disk.
    """
    disk_client = compute_v1.DisksClient()
    disk = compute_v1.Disk()
    disk.zone = zone
    disk.size_gb = disk_size_gb
    disk.source_disk = disk_link
    disk.type_ = disk_type
    disk.name = disk_name
    operation = disk_client.insert(project=project_id, zone=zone, disk_resource=disk)

    wait_for_extended_operation(operation, "disk creation")

    return disk_client.get(project=project_id, zone=zone, disk=disk_name)

REST

To clone a zonal source disk and create a new zonal disk, make a POST request to the compute.disks.insert method. In the request body, specify the name and sourceDisk parameters. The disk clone inherits all omitted properties from the source disk.

POST https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/disks

{
  "name": "TARGET_DISK_NAME"
  "sourceDisk": "projects/PROJECT_ID/zones/ZONE/disks/SOURCE_DISK_NAME"
}

Replace the following:

  • PROJECT_ID: the project ID where you want to clone the disk.
  • ZONE: the zone of the source and new disk.
  • TARGET_DISK_NAME: the name for the new disk.
  • SOURCE_DISK_NAME: the name of the source disk

Create a regional disk clone from a zonal disk

You can create a new regional Persistent Disk disk by cloning an existing zonal Persistent Disk volume. To migrate a zonal disk to a regional disk, Google recommends this option instead of creating a snapshot of the zonal disk and restoring the snapshot to a new regional disk.

Console

  1. In the Google Cloud console, go to the Disks page.

    Go to Disks

  2. In the list of disks, navigate to the zonal Persistent Disk volume that you want to clone.

  3. In the Actions column, click the menu button and select Clone disk.

    Create clone.

    In the Clone disk panel that appears, do the following:

    1. In the Name field, specify a name for the cloned disk.
    2. For Location, select Regional and then select the secondary replica zone for the new regional cloned disk.
    3. Under Properties, review other details for the cloned disk.
    4. To finish creating the cloned disk, click Create.

gcloud

To create a regional disk clone from a zonal disk, run the gcloud compute disks create command and specify the --region and --replica-zones parameters.

gcloud compute disks create TARGET_DISK_NAME \
  --description="zonal to regional cloned disk" \
  --region=CLONED_REGION \
  --source-disk=SOURCE_DISK_NAME \
  --source-disk-zone=SOURCE_DISK_ZONE \
  --replica-zones=SOURCE_DISK_ZONE,REPLICA_ZONE_2 \
  --project=PROJECT_ID

Replace the following:

  • TARGET_DISK_NAME: the name for the new regional disk clone.
  • CLONED_REGION: the region of the source and cloned disks.
  • SOURCE_DISK_NAME: the name of the zonal disk to clone.
  • SOURCE_DISK_ZONE: the zone for the source disk. This will also be the first replica zone for the regional disk clone.
  • REPLICA_ZONE_2: the second replica zone for the new regional disk clone.
  • PROJECT_ID: the project ID where you want to clone the disk.

Terraform

To create a regional disk clone from a zonal disk, you can optionally create a snapshot of the zonal disk and then clone the snapshot. To do this, use the following resources:

resource "google_compute_region_disk" "regiondisk" {
  name                      = "region-disk-name"
  snapshot                  = google_compute_snapshot.snapdisk.id
  type                      = "pd-ssd"
  region                    = "us-central1"
  physical_block_size_bytes = 4096
  size                      = 11

  replica_zones = ["us-central1-a", "us-central1-f"]
}

To learn how to apply or remove a Terraform configuration, see Basic Terraform commands.

Go

Go

Before trying this sample, follow the Go setup instructions in the Compute Engine quickstart using client libraries. For more information, see the Compute Engine Go API reference documentation.

To authenticate to Compute Engine, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

import (
	"context"
	"fmt"
	"io"

	compute "cloud.google.com/go/compute/apiv1"
	computepb "cloud.google.com/go/compute/apiv1/computepb"
	"google.golang.org/protobuf/proto"
)

// createRegionalDiskFromDisk creates a new regional disk with the contents of
// an already existitng zonal disk. Disk type and size may differ.
func createRegionalDiskFromDisk(
	w io.Writer,
	projectID, region string, replicaZones []string,
	diskName, diskType, sourceDiskLink string,
	diskSizeGb int64,
) error {
	// projectID := "your_project_id"
	// region := "us-west3" // should match diskType below
	// diskName := "your_disk_name"
	// diskType := "regions/us-west3/diskTypes/pd-ssd"
	// sourceDiskLink := "projects/your_project_id/global/disks/disk_name"
	// diskSizeGb := 120

	// Exactly two replica zones must be specified
	replicaZoneURLs := []string{
		fmt.Sprintf("projects/%s/zones/%s", projectID, replicaZones[0]),
		fmt.Sprintf("projects/%s/zones/%s", projectID, replicaZones[1]),
	}

	ctx := context.Background()
	disksClient, err := compute.NewRegionDisksRESTClient(ctx)
	if err != nil {
		return fmt.Errorf("NewRegionDisksRESTClient: %w", err)
	}
	defer disksClient.Close()

	req := &computepb.InsertRegionDiskRequest{
		Project: projectID,
		Region:  region,
		DiskResource: &computepb.Disk{
			Name:         proto.String(diskName),
			Region:       proto.String(region),
			Type:         proto.String(diskType),
			SourceDisk:   proto.String(sourceDiskLink),
			SizeGb:       proto.Int64(diskSizeGb),
			ReplicaZones: replicaZoneURLs,
		},
	}

	op, err := disksClient.Insert(ctx, req)
	if err != nil {
		return fmt.Errorf("unable to create disk: %w", err)
	}

	if err = op.Wait(ctx); err != nil {
		return fmt.Errorf("unable to wait for the operation: %w", err)
	}

	fmt.Fprintf(w, "Disk created\n")

	return nil
}

Java

Java

Before trying this sample, follow the Java setup instructions in the Compute Engine quickstart using client libraries. For more information, see the Compute Engine Java API reference documentation.

To authenticate to Compute Engine, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.


import com.google.cloud.compute.v1.Disk;
import com.google.cloud.compute.v1.Operation;
import com.google.cloud.compute.v1.RegionDisksClient;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class RegionalCreateFromSource {

  public static void main(String[] args)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // TODO(developer): Replace these variables before running the sample.
    // Project ID or project number of the Cloud project you want to use.
    String project = "YOUR_PROJECT_ID";

    // Name of the zone in which you want to create the disk.
    String region = "europe-central2";

    // An iterable collection of zone names in which you want to keep
    // the new disks' replicas. One of the replica zones of the clone must match
    // the zone of the source disk.
    List<String> replicaZones = new ArrayList<>();

    // Name of the disk you want to create.
    String diskName = "YOUR_DISK_NAME";

    // The type of disk you want to create. This value uses the following format:
    // "zones/{zone}/diskTypes/(pd-standard|pd-ssd|pd-balanced|pd-extreme)".
    // For example: "zones/us-west3-b/diskTypes/pd-ssd"
    String diskType = String.format("zones/%s/diskTypes/pd-ssd", "ZONE_NAME");

    // Size of the new disk in gigabytes.
    int diskSizeGb = 10;

    // A link to the disk you want to use as a source for the new disk.
    // This value uses the following format:
    // "projects/{project_name}/zones/{zone}/disks/{disk_name}"
    String diskLink = String.format("projects/%s/zones/%s/disks/%s", "PROJECT_NAME", "ZONE",
        "DISK_NAME");

    // A link to the snapshot you want to use as a source for the new disk.
    // This value uses the following format:
    // "projects/{project_name}/global/snapshots/{snapshot_name}"
    String snapshotLink = String.format("projects/%s/global/snapshots/%s", "PROJECT_NAME",
        "SNAPSHOT_NAME");

    createRegionalDisk(project, region, replicaZones, diskName, diskType, diskSizeGb,
        Optional.ofNullable(diskLink), Optional.ofNullable(snapshotLink));
  }

  // Creates a regional disk from an existing zonal disk in a given project.
  public static void createRegionalDisk(
      String project, String region, List<String> replicaZones, String diskName, String diskType,
      int diskSizeGb, Optional<String> diskLink, Optional<String> snapshotLink)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // Initialize client that will be used to send requests. This client only needs to be created
    // once, and can be reused for multiple requests. After completing all of your requests, call
    // the `regionDisksClient.close()` method on the client to safely
    // clean up any remaining background resources.
    try (RegionDisksClient regionDisksClient = RegionDisksClient.create()) {

      Disk.Builder diskBuilder = Disk.newBuilder()
          .addAllReplicaZones(replicaZones)
          .setName(diskName)
          .setType(diskType)
          .setSizeGb(diskSizeGb)
          .setRegion(region);

      // Set source disk if diskLink is not empty.
      diskLink.ifPresent(diskBuilder::setSourceDisk);

      // Set source snapshot if the snapshot link is not empty.
      snapshotLink.ifPresent(diskBuilder::setSourceSnapshot);

      // Wait for the operation to complete.
      Operation operation = regionDisksClient.insertAsync(project, region, diskBuilder.build())
          .get(3, TimeUnit.MINUTES);

      if (operation.hasError()) {
        System.out.println("Disk creation failed!");
        throw new Error(operation.getError().toString());
      }
      System.out.println(
          "Regional disk created. Operation Status: " + operation.getStatus());
    }
  }
}

Python

Python

Before trying this sample, follow the Python setup instructions in the Compute Engine quickstart using client libraries. For more information, see the Compute Engine Python API reference documentation.

To authenticate to Compute Engine, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

from __future__ import annotations

from collections.abc import Iterable
import sys
from typing import Any

from google.api_core.extended_operation import ExtendedOperation
from google.cloud import compute_v1


def wait_for_extended_operation(
    operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300
) -> Any:
    """
    Waits for the extended (long-running) operation to complete.

    If the operation is successful, it will return its result.
    If the operation ends with an error, an exception will be raised.
    If there were any warnings during the execution of the operation
    they will be printed to sys.stderr.

    Args:
        operation: a long-running operation you want to wait on.
        verbose_name: (optional) a more verbose name of the operation,
            used only during error and warning reporting.
        timeout: how long (in seconds) to wait for operation to finish.
            If None, wait indefinitely.

    Returns:
        Whatever the operation.result() returns.

    Raises:
        This method will raise the exception received from `operation.exception()`
        or RuntimeError if there is no exception set, but there is an `error_code`
        set for the `operation`.

        In case of an operation taking longer than `timeout` seconds to complete,
        a `concurrent.futures.TimeoutError` will be raised.
    """
    result = operation.result(timeout=timeout)

    if operation.error_code:
        print(
            f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}",
            file=sys.stderr,
            flush=True,
        )
        print(f"Operation ID: {operation.name}", file=sys.stderr, flush=True)
        raise operation.exception() or RuntimeError(operation.error_message)

    if operation.warnings:
        print(f"Warnings during {verbose_name}:\n", file=sys.stderr, flush=True)
        for warning in operation.warnings:
            print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True)

    return result


def create_regional_disk(
    project_id: str,
    region: str,
    replica_zones: Iterable[str],
    disk_name: str,
    disk_type: str,
    disk_size_gb: int,
    disk_link: str | None = None,
    snapshot_link: str | None = None,
) -> compute_v1.Disk:
    """
    Creates a regional disk from an existing zonal disk in a given project.

    Args:
        project_id: project ID or project number of the Cloud project you want to use.
        region: name of the region in which you want to create the disk.
        replica_zones: an iterable collection of zone names in which you want to keep
            the new disks' replicas. One of the replica zones of the clone must match
            the zone of the source disk.
        disk_name: name of the disk you want to create.
        disk_type: the type of disk you want to create. This value uses the following format:
            "regions/{region}/diskTypes/(pd-standard|pd-ssd|pd-balanced|pd-extreme)".
            For example: "regions/us-west3/diskTypes/pd-ssd"
        disk_size_gb: size of the new disk in gigabytes
        disk_link: a link to the disk you want to use as a source for the new disk.
            This value uses the following format: "projects/{project_name}/zones/{zone}/disks/{disk_name}"
        snapshot_link: a link to the snapshot you want to use as a source for the new disk.
            This value uses the following format: "projects/{project_name}/global/snapshots/{snapshot_name}"

    Returns:
        An attachable regional disk.
    """
    disk_client = compute_v1.RegionDisksClient()
    disk = compute_v1.Disk()
    disk.replica_zones = replica_zones
    disk.size_gb = disk_size_gb
    if disk_link:
        disk.source_disk = disk_link
    if snapshot_link:
        disk.source_snapshot = snapshot_link
    disk.type_ = disk_type
    disk.region = region
    disk.name = disk_name
    operation = disk_client.insert(
        project=project_id, region=region, disk_resource=disk
    )

    wait_for_extended_operation(operation, "disk creation")

    return disk_client.get(project=project_id, region=region, disk=disk_name)

REST

To create a regional disk clone from a zonal disk, make a POST request to the compute.disks.insert method and specify the sourceDisk and replicaZone parameters.

POST https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/regions/CLONED_REGION/disks

{
  "name": "TARGET_DISK_NAME"
  "sourceDisk": "projects/PROJECT_ID/zones/SOURCE_DISK_ZONE/disks/SOURCE_DISK_NAME"
  "replicaZone": "SOURCE_DISK_ZONE,REPLICA_ZONE_2"
}

Replace the following:

  • PROJECT_ID: the project ID where you want to clone the disk.
  • TARGET_DISK_NAME: the name for the new regional disk clone.
  • CLONED_REGION: the region of the source and cloned disks.
  • SOURCE_DISK_NAME: the name of the zonal disk to clone.
  • SOURCE_DISK_ZONE: the zone for the source disk. This will also be the first replica zone for the regional disk clone.
  • REPLICA_ZONE_2: the second replica zone for the new regional disk clone.

Create a disk clone of an encrypted source disk

You can use a customer-supplied encryption key (CSEK) or a customer-managed encryption key to encrypt your disks.

Create disk clones for CSEK-encrypted disks

If you use a CSEK to encrypt your source disk, you must also use the same key to encrypt the clone.

Console

  1. In the Google Cloud console, go to the Disks page.

    Go to Disks

  2. In the list of zonal persistent disks, find the disk that you want to clone.

  3. In the Actions column, click the menu button and select Clone disk.

    Create clone.

    In the Clone disk panel that appears, do the following:

    1. In the Name field, specify a name for the cloned disk.
    2. In the Decryption and encryption field, provide the source disk encryption key.
    3. Under Properties, review other details for the cloned disk.
    4. To finish creating the cloned disk, click Create.

gcloud

To create a disk clone for a CSEK-encrypted source disk, run the gcloud compute disks create command and provide the source disk encryption key using the --csek-key-file flag. If you are using an RSA-wrapped key, use the gcloud beta compute disks create command.

gcloud compute disks create TARGET_DISK_NAME \
  --description="cloned disk" \
  --source-disk=projects/PROJECT_ID/zones/ZONE/disks/SOURCE_DISK_NAME \
  --csek-key-file example-key-file.json

Replace the following:

  • TARGET_DISK_NAME: the name for the new disk.
  • PROJECT_ID: the project ID where you want to clone the disk.
  • ZONE: the zone of the source and new disk.
  • SOURCE_DISK_NAME: the name of the source disk

Go

Go

Before trying this sample, follow the Go setup instructions in the Compute Engine quickstart using client libraries. For more information, see the Compute Engine Go API reference documentation.

To authenticate to Compute Engine, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

import (
	"context"
	"fmt"
	"io"

	compute "cloud.google.com/go/compute/apiv1"
	computepb "cloud.google.com/go/compute/apiv1/computepb"
	"google.golang.org/protobuf/proto"
)

// Creates a zonal non-boot persistent disk in a project with the copy of data from an existing disk.
// The encryption key must be the same for the source disk and the new disk.
// The disk type and size may differ.
func createDiskFromCustomerEncryptedDisk(
	w io.Writer,
	projectID, zone, diskName, diskType string,
	diskSizeGb int64,
	diskLink, encryptionKey string,
) error {
	// projectID := "your_project_id"
	// zone := "us-west3-b" // should match diskType below
	// diskName := "your_disk_name"
	// diskType := "zones/us-west3/diskTypes/pd-ssd"
	// diskSizeGb := 120
	// diskLink := "projects/your_project_id/global/disks/disk_name"
	// encryptionKey := "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=" // in base64

	ctx := context.Background()
	disksClient, err := compute.NewDisksRESTClient(ctx)
	if err != nil {
		return fmt.Errorf("NewDisksRESTClient: %w", err)
	}
	defer disksClient.Close()

	req := &computepb.InsertDiskRequest{
		Project: projectID,
		Zone:    zone,
		DiskResource: &computepb.Disk{
			Name:       proto.String(diskName),
			Zone:       proto.String(zone),
			Type:       proto.String(diskType),
			SizeGb:     proto.Int64(diskSizeGb),
			SourceDisk: proto.String(diskLink),
			DiskEncryptionKey: &computepb.CustomerEncryptionKey{
				RawKey: &encryptionKey,
			},
		},
	}

	op, err := disksClient.Insert(ctx, req)
	if err != nil {
		return fmt.Errorf("unable to create disk: %w", err)
	}

	if err = op.Wait(ctx); err != nil {
		return fmt.Errorf("unable to wait for the operation: %w", err)
	}

	fmt.Fprintf(w, "Disk created\n")

	return nil
}

Java

Java

Before trying this sample, follow the Java setup instructions in the Compute Engine quickstart using client libraries. For more information, see the Compute Engine Java API reference documentation.

To authenticate to Compute Engine, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.


import com.google.cloud.compute.v1.CustomerEncryptionKey;
import com.google.cloud.compute.v1.Disk;
import com.google.cloud.compute.v1.DisksClient;
import com.google.cloud.compute.v1.InsertDiskRequest;
import com.google.cloud.compute.v1.Operation;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CloneEncryptedDisk {

  public static void main(String[] args)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // TODO(developer): Replace these variables before running the sample.
    // Project ID or project number of the Cloud project you want to use.
    String project = "YOUR_PROJECT_ID";

    // Name of the zone in which you want to create the disk.
    String zone = "europe-central2-b";

    // Name of the disk you want to create.
    String diskName = "YOUR_DISK_NAME";

    // The type of disk you want to create. This value uses the following format:
    // "zones/{zone}/diskTypes/(pd-standard|pd-ssd|pd-balanced|pd-extreme)".
    // For example: "zones/us-west3-b/diskTypes/pd-ssd"
    String diskType = String.format("zones/%s/diskTypes/pd-ssd", zone);

    // Size of the new disk in gigabytes.
    int diskSizeGb = 10;

    // A link to the disk you want to use as a source for the new disk.
    // This value uses the following format:
    // "projects/{project_name}/zones/{zone}/disks/{disk_name}"
    String diskLink = String.format("projects/%s/zones/%s/disks/%s", "PROJECT_NAME", "ZONE",
        "DISK_NAME");

    // Customer-supplied encryption key used for encrypting data in the source disk.
    // The data will be encrypted with the same key in the new disk.
    byte[] encryptionKey = null;

    createDiskFromCustomerEncryptedKey(project, zone, diskName, diskType, diskSizeGb, diskLink,
        encryptionKey);
  }

  // Creates a zonal non-boot persistent disk in a project with the copy of data
  // from an existing disk.
  // The encryption key must be the same for the source disk and the new disk.
  public static void createDiskFromCustomerEncryptedKey(String project, String zone,
      String diskName, String diskType, int diskSizeGb, String diskLink, byte[] encryptionKey)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // Initialize client that will be used to send requests. This client only needs to be created
    // once, and can be reused for multiple requests. After completing all of your requests, call
    // the `disksClient.close()` method on the client to safely
    // clean up any remaining background resources.
    try (DisksClient disksClient = DisksClient.create()) {

      // Create a disk and set the encryption key.
      Disk disk = Disk.newBuilder()
          .setZone(zone)
          .setName(diskName)
          .setType(diskType)
          .setSizeGb(diskSizeGb)
          .setSourceDisk(diskLink)
          .setDiskEncryptionKey(CustomerEncryptionKey
              .newBuilder()
              .setRawKeyBytes(ByteString.copyFrom(encryptionKey))
              .build())
          .build();

      // Wait for the insert disk operation to complete.
      Operation operation = disksClient.insertAsync(
          InsertDiskRequest.newBuilder()
              .setProject(project)
              .setZone(zone)
              .setDiskResource(disk)
              .build()).get(3, TimeUnit.MINUTES);

      if (operation.hasError()) {
        System.out.println("Disk creation failed!");
        throw new Error(operation.getError().toString());
      }
      System.out.println(
          "Disk cloned with customer encryption key. Operation Status: " + operation.getStatus());
    }
  }
}

Python

Python

Before trying this sample, follow the Python setup instructions in the Compute Engine quickstart using client libraries. For more information, see the Compute Engine Python API reference documentation.

To authenticate to Compute Engine, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

from __future__ import annotations

import sys
from typing import Any

from google.api_core.extended_operation import ExtendedOperation
from google.cloud import compute_v1


def wait_for_extended_operation(
    operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300
) -> Any:
    """
    Waits for the extended (long-running) operation to complete.

    If the operation is successful, it will return its result.
    If the operation ends with an error, an exception will be raised.
    If there were any warnings during the execution of the operation
    they will be printed to sys.stderr.

    Args:
        operation: a long-running operation you want to wait on.
        verbose_name: (optional) a more verbose name of the operation,
            used only during error and warning reporting.
        timeout: how long (in seconds) to wait for operation to finish.
            If None, wait indefinitely.

    Returns:
        Whatever the operation.result() returns.

    Raises:
        This method will raise the exception received from `operation.exception()`
        or RuntimeError if there is no exception set, but there is an `error_code`
        set for the `operation`.

        In case of an operation taking longer than `timeout` seconds to complete,
        a `concurrent.futures.TimeoutError` will be raised.
    """
    result = operation.result(timeout=timeout)

    if operation.error_code:
        print(
            f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}",
            file=sys.stderr,
            flush=True,
        )
        print(f"Operation ID: {operation.name}", file=sys.stderr, flush=True)
        raise operation.exception() or RuntimeError(operation.error_message)

    if operation.warnings:
        print(f"Warnings during {verbose_name}:\n", file=sys.stderr, flush=True)
        for warning in operation.warnings:
            print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True)

    return result


def create_disk_from_customer_encrypted_disk(
    project_id: str,
    zone: str,
    disk_name: str,
    disk_type: str,
    disk_size_gb: int,
    disk_link: str,
    encryption_key: bytes,
) -> compute_v1.Disk:
    """
    Creates a zonal non-boot persistent disk in a project with the copy of data from an existing disk.

    The encryption key must be the same for the source disk and the new disk.

    Args:
        project_id: project ID or project number of the Cloud project you want to use.
        zone: name of the zone in which you want to create the disk.
        disk_name: name of the disk you want to create.
        disk_type: the type of disk you want to create. This value uses the following format:
            "zones/{zone}/diskTypes/(pd-standard|pd-ssd|pd-balanced|pd-extreme)".
            For example: "zones/us-west3-b/diskTypes/pd-ssd"
        disk_size_gb: size of the new disk in gigabytes
        disk_link: a link to the disk you want to use as a source for the new disk.
            This value uses the following format: "projects/{project_name}/zones/{zone}/disks/{disk_name}"
        encryption_key: customer-supplied encryption key used for encrypting
            data in the source disk. The data will be encrypted with the same key
            in the new disk.

    Returns:
        An attachable copy of an existing disk.
    """
    disk_client = compute_v1.DisksClient()
    disk = compute_v1.Disk()
    disk.zone = zone
    disk.size_gb = disk_size_gb
    disk.source_disk = disk_link
    disk.type_ = disk_type
    disk.name = disk_name
    disk.disk_encryption_key = compute_v1.CustomerEncryptionKey()
    disk.disk_encryption_key.raw_key = encryption_key
    operation = disk_client.insert(project=project_id, zone=zone, disk_resource=disk)

    wait_for_extended_operation(operation, "disk creation")

    return disk_client.get(project=project_id, zone=zone, disk=disk_name)

REST

To create a disk clone for a CSEK-encrypted source disk, make a POST request to the compute.disks.insert method and provide the source disk encryption key using the diskEncryptionKey property. If you are using an RSA-wrapped key, use the beta version of the method.

POST https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/disks

{
  "name": "TARGET_DISK_NAME"
  "sourceDisk": "projects/PROJECT_ID/zones/ZONE/disks/SOURCE_DISK_NAME"
  "diskEncryptionKey": {
    "rsaEncryptedKey": "ieCx/NcW06PcT7Ep1X6LUTc/hLvUDYyzSZPPVCVPTVEohpeHASqC8uw5TzyO9U+Fka9JFHz0mBibXUInrC/jEk014kCK/NPjYgEMOyssZ4ZINPKxlUh2zn1bV+MCaTICrdmuSBTWlUUiFoDD6PYznLwh8ZNdaheCeZ8ewEXgFQ8V+sDroLaN3Xs3MDTXQEMMoNUXMCZEIpg9Vtp9x2oeQ5lAbtt7bYAAHf5l+gJWw3sUfs0/Glw5fpdjT8Uggrr+RMZezGrltJEF293rvTIjWOEB3z5OHyHwQkvdrPDFcTqsLfh+8Hr8g+mf+7zVPEC8nEbqpdl3GPv3A7AwpFp7MA=="
  },
}

Replace the following:

  • PROJECT_ID: the project ID where you want to clone the disk.
  • ZONE: the zone of the source and new disk.
  • TARGET_DISK_NAME: the name for the new disk.
  • SOURCE_DISK_NAME: the name of the source disk

Create disk clones for CMEK-encrypted disks

If you use a CMEK to encrypt your source disk, you must also use the same key to encrypt the clone.

Console

Compute Engine automatically encrypts the clone using the source disk encryption key.

gcloud

To create a disk clone for a CSEK-encrypted source disk, run the gcloud compute disks create command and provide the source disk encryption key using the --kms-key flag. If you are using an RSA-wrapped key, use the gcloud beta compute disks create command.

gcloud compute disks create TARGET_DISK_NAME \
  --description="cloned disk" \
  --source-disk=projects/PROJECT_ID/zones/ZONE/disks/SOURCE_DISK_NAME \
  --kms-key projects/KMS_PROJECT_ID/locations/REGION/keyRings/KEY_RING/cryptoKeys/KEY

Replace the following:

  • TARGET_DISK_NAME: the name for the new disk.
  • PROJECT_ID: the project ID where you want to clone the disk.
  • ZONE: the zone of the source and new disk.
  • SOURCE_DISK_NAME: the name of the source disk.
  • KMS_PROJECT_ID: the project ID for the encryption key.
  • REGION: the region of the encryption key.
  • KEY_RING: the key ring of the encryption key.
  • KEY: the name of the encryption key.

Go

Go

Before trying this sample, follow the Go setup instructions in the Compute Engine quickstart using client libraries. For more information, see the Compute Engine Go API reference documentation.

To authenticate to Compute Engine, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

import (
	"context"
	"fmt"
	"io"

	compute "cloud.google.com/go/compute/apiv1"
	computepb "cloud.google.com/go/compute/apiv1/computepb"
	"google.golang.org/protobuf/proto"
)

// Creates a zonal non-boot persistent disk in a project with the copy of data from an existing disk.
// The encryption key must be the same for the source disk and the new disk.
// The disk type and size may differ.
func createDiskFromKmsEncryptedDisk(
	w io.Writer,
	projectID, zone, diskName, diskType string,
	diskSizeGb int64,
	diskLink, kmsKeyLink string,
) error {
	// projectID := "your_project_id"
	// zone := "us-west3-b" // should match diskType below
	// diskName := "your_disk_name"
	// diskType := "zones/us-west3/diskTypes/pd-ssd"
	// diskSizeGb := 120
	// diskLink := "projects/your_project_id/global/disks/disk_name"
	// kmsKeyLink := "projects/your_kms_project_id/locations/us-central1/keyRings/your_key_ring/cryptoKeys/your_key"

	ctx := context.Background()
	disksClient, err := compute.NewDisksRESTClient(ctx)
	if err != nil {
		return fmt.Errorf("NewDisksRESTClient: %w", err)
	}
	defer disksClient.Close()

	req := &computepb.InsertDiskRequest{
		Project: projectID,
		Zone:    zone,
		DiskResource: &computepb.Disk{
			Name:       proto.String(diskName),
			Zone:       proto.String(zone),
			Type:       proto.String(diskType),
			SizeGb:     proto.Int64(diskSizeGb),
			SourceDisk: proto.String(diskLink),
			DiskEncryptionKey: &computepb.CustomerEncryptionKey{
				KmsKeyName: &kmsKeyLink,
			},
		},
	}

	op, err := disksClient.Insert(ctx, req)
	if err != nil {
		return fmt.Errorf("unable to create disk: %w", err)
	}

	if err = op.Wait(ctx); err != nil {
		return fmt.Errorf("unable to wait for the operation: %w", err)
	}

	fmt.Fprintf(w, "Disk created\n")

	return nil
}

Java

Java

Before trying this sample, follow the Java setup instructions in the Compute Engine quickstart using client libraries. For more information, see the Compute Engine Java API reference documentation.

To authenticate to Compute Engine, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.


import com.google.cloud.compute.v1.CustomerEncryptionKey;
import com.google.cloud.compute.v1.Disk;
import com.google.cloud.compute.v1.DisksClient;
import com.google.cloud.compute.v1.InsertDiskRequest;
import com.google.cloud.compute.v1.Operation;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CloneEncryptedDiskManagedKey {

  public static void main(String[] args)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // TODO(developer): Replace these variables before running the sample.
    // Project ID or project number of the Cloud project you want to use.
    String project = "YOUR_PROJECT_ID";

    // Name of the zone in which you want to create the disk.
    String zone = "europe-central2-b";

    // Name of the disk you want to create.
    String diskName = "YOUR_DISK_NAME";

    // The type of disk you want to create. This value uses the following format:
    // "zones/{zone}/diskTypes/(pd-standard|pd-ssd|pd-balanced|pd-extreme)".
    // For example: "zones/us-west3-b/diskTypes/pd-ssd"
    String diskType = String.format("zones/%s/diskTypes/pd-ssd", zone);

    // Size of the new disk in gigabytes.
    int diskSizeGb = 10;

    // A link to the disk you want to use as a source for the new disk.
    // This value uses the following format:
    // "projects/{project_name}/zones/{zone}/disks/{disk_name}"
    String diskLink = String.format("projects/%s/zones/%s/disks/%s", "PROJECT_NAME", "ZONE",
        "DISK_NAME");

    // URL of the key from KMS. The key might be from another project, as
    // long as you have access to it. The data will be encrypted with the same key
    // in the new disk. This value uses following format:
    // "projects/{kms_project_id}/locations/{region}/keyRings/{key_ring}/cryptoKeys/{key}"
    String kmsKeyName = "kms-key-name";

    createDiskFromKmsEncryptedDisk(project, zone, diskName, diskType, diskSizeGb, diskLink,
        kmsKeyName);
  }

  // Creates a zonal non-boot disk in a project with the copy of data from an existing disk.
  // The encryption key must be the same for the source disk and the new disk.
  public static void createDiskFromKmsEncryptedDisk(String project, String zone, String diskName,
      String diskType, int diskSizeGb, String diskLink, String kmsKeyName)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // Initialize client that will be used to send requests. This client only needs to be created
    // once, and can be reused for multiple requests. After completing all of your requests, call
    // the `disksClient.close()` method on the client to safely
    // clean up any remaining background resources.
    try (DisksClient disksClient = DisksClient.create()) {

      // Create a disk and set the KMS encryption key name.
      Disk disk = Disk.newBuilder()
          .setZone(zone)
          .setName(diskName)
          .setType(diskType)
          .setSizeGb(diskSizeGb)
          .setSourceDisk(diskLink)
          .setDiskEncryptionKey(CustomerEncryptionKey.newBuilder()
              .setKmsKeyName(kmsKeyName)
              .build())
          .build();

      // Wait for the insert disk operation to complete.
      Operation operation = disksClient.insertAsync(
          InsertDiskRequest.newBuilder()
              .setProject(project)
              .setZone(zone)
              .setDiskResource(disk)
              .build()).get(3, TimeUnit.MINUTES);

      if (operation.hasError()) {
        System.out.println("Disk creation failed!");
        throw new Error(operation.getError().toString());
      }
      System.out.println(
          "Disk cloned with KMS encryption key. Operation Status: " + operation.getStatus());
    }
  }
}

Python

Python

Before trying this sample, follow the Python setup instructions in the Compute Engine quickstart using client libraries. For more information, see the Compute Engine Python API reference documentation.

To authenticate to Compute Engine, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

from __future__ import annotations

import sys
from typing import Any

from google.api_core.exceptions import BadRequest
from google.api_core.extended_operation import ExtendedOperation
from google.cloud import compute_v1


def wait_for_extended_operation(
    operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300
) -> Any:
    """
    Waits for the extended (long-running) operation to complete.

    If the operation is successful, it will return its result.
    If the operation ends with an error, an exception will be raised.
    If there were any warnings during the execution of the operation
    they will be printed to sys.stderr.

    Args:
        operation: a long-running operation you want to wait on.
        verbose_name: (optional) a more verbose name of the operation,
            used only during error and warning reporting.
        timeout: how long (in seconds) to wait for operation to finish.
            If None, wait indefinitely.

    Returns:
        Whatever the operation.result() returns.

    Raises:
        This method will raise the exception received from `operation.exception()`
        or RuntimeError if there is no exception set, but there is an `error_code`
        set for the `operation`.

        In case of an operation taking longer than `timeout` seconds to complete,
        a `concurrent.futures.TimeoutError` will be raised.
    """
    result = operation.result(timeout=timeout)

    if operation.error_code:
        print(
            f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}",
            file=sys.stderr,
            flush=True,
        )
        print(f"Operation ID: {operation.name}", file=sys.stderr, flush=True)
        raise operation.exception() or RuntimeError(operation.error_message)

    if operation.warnings:
        print(f"Warnings during {verbose_name}:\n", file=sys.stderr, flush=True)
        for warning in operation.warnings:
            print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True)

    return result


def create_disk_from_kms_encrypted_disk(
    project_id: str,
    zone: str,
    disk_name: str,
    disk_type: str,
    disk_size_gb: int,
    disk_link: str,
    kms_key_name: str,
) -> compute_v1.Disk:
    """
    Creates a zonal non-boot disk in a project with the copy of data from an existing disk.

    The encryption key must be the same for the source disk and the new disk.

    To run this method, the service-<project_id>@compute-system.iam.gserviceaccount.com
    service account needs to have the cloudkms.cryptoKeyEncrypterDecrypter role,
    as described in documentation:
    https://cloud.google.com/compute/docs/disks/customer-managed-encryption#before_you_begin

    Args:
        project_id: project ID or project number of the Cloud project you want to use.
        zone: name of the zone in which you want to create the disk.
        disk_name: name of the disk you want to create.
        disk_type: the type of disk you want to create. This value uses the following format:
            "zones/{zone}/diskTypes/(pd-standard|pd-ssd|pd-balanced|pd-extreme)".
            For example: "zones/us-west3-b/diskTypes/pd-ssd"
        disk_size_gb: size of the new disk in gigabytes
        disk_link: a link to the disk you want to use as a source for the new disk.
            This value uses the following format: "projects/{project_name}/zones/{zone}/disks/{disk_name}"
        kms_key_name: URL of the key from KMS. The key might be from another project, as
            long as you have access to it. The data will be encrypted with the same key
            in the new disk. This value uses following format:
            "projects/{kms_project_id}/locations/{region}/keyRings/{key_ring}/cryptoKeys/{key}"

    Returns:
        An attachable copy of an existing disk.
    """
    disk_client = compute_v1.DisksClient()
    disk = compute_v1.Disk()
    disk.zone = zone
    disk.size_gb = disk_size_gb
    disk.source_disk = disk_link
    disk.type_ = disk_type
    disk.name = disk_name
    disk.disk_encryption_key = compute_v1.CustomerEncryptionKey()
    disk.disk_encryption_key.kms_key_name = kms_key_name
    try:
        operation = disk_client.insert(
            project=project_id, zone=zone, disk_resource=disk
        )
    except BadRequest as err:
        if "Permission 'cloudkms.cryptoKeyVersions.useToEncrypt' denied" in err.message:
            print(
                f"Please provide the cloudkms.cryptoKeyEncrypterDecrypter role to"
                f"service-{project_id}@compute-system.iam.gserviceaccount.com"
            )
        raise err

    wait_for_extended_operation(operation, "disk creation")

    return disk_client.get(project=project_id, zone=zone, disk=disk_name)

REST

To create a disk clone for a CSEK-encrypted source disk, make a POST request to the compute.disks.insert method and provide the source disk encryption key using the kmsKeyName property. If you are using an RSA-wrapped key, use the beta version of the method.

POST https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/disks

{
  "name": "TARGET_DISK_NAME"
  "sourceDisk": "projects/PROJECT_ID/zones/ZONE/disks/SOURCE_DISK_NAME"
  "diskEncryptionKey": {
    "kmsKeyName": "projects/KMS_PROJECT_ID/locations/REGION/keyRings/KEY_RING/cryptoKeys/KEY"
  },
}

Replace the following:

  • PROJECT_ID: the project ID where you want to clone the disk.
  • ZONE: the zone of the source and new disk.
  • TARGET_DISK_NAME: the name for the new disk.
  • SOURCE_DISK_NAME: the name of the source disk.
  • KMS_PROJECT_ID: the project ID for the encryption key.
  • REGION: the region of the encryption key.
  • KEY_RING: the key ring of the encryption key.
  • KEY: the name of the encryption key.

What's next