diff --git a/compute/client_library/README.md b/compute/client_library/README.md new file mode 100644 index 00000000000..cc715bec0c3 --- /dev/null +++ b/compute/client_library/README.md @@ -0,0 +1,53 @@ +# Code samples for the Compute Engine library + +In this folder you can find the source code for the code samples used throughout the +[public documentation](https://cloud.google.com/compute/docs/) of Google Compute Engine. + +The samples can be found in the `snippets` folder, where they are organized to mimic the +structure of the public documentation. Files that are saved there are generated by the `sgs.py` +script from pieces found in `ingredients` and `recipes`. This way, one piece of code can be easily +included in multiple snippets and updating the code requires less work. + +## Working with the SGS + +SGS (Snippet Generating System) works by scanning the `recipes` folder, finding all files +and filling them with pieces of code found in `ingredients`. The folder structure of `recipes` is +reconstructed in the `snippets` folder. + +### Adding new sample + +To create a new sample, just prepare a new file in one of the `recipes` subfolders. The SGS will pick it up +automatically when you run it, by executing `python3 sgs.py generate` in this (`samples/`) directory. + +### Removing/moving a sample + +To remove or move a sample, you need to simply modify the `recipes` folder to match your desired structure, then delete +the generated snippet from the `snippets` directory. The SGS script will create the snippet in the new location next +time you run `python3 sgs.py generate`. + +### Interacting with GIT + +SGS will not interact with Git repository in any way. All changes made by the script need to be committed manually - +preferably in the same commit as the update to the source files. + +## Preparing an ingredient +To add a new ingredient, create a new `.py` file with the code you want to later use in the snippets. Mark the beginning +of the code you want to include with `# ` and the end with `# `. + +Please leave the imports required by this ingredient **OUTSIDE** the area marked with ingredient comments. The SGS +script will automatically collect all the required imports and put them in the final snippet in the right place and in +right order. + +## Preparing a recipe +Each recipe is a file located in the `recipes` folder. It should have the `.py` extension and should be a valid Python +script. Each recipe has to have an `# ` line and at least one `# ` line. +Apart from those restrictions, the contents of the file can be whatever you want. + +The SGS will copy the recipe file to the destination folder in `snippets` and replace the `# ` and +`# ` lines with the `import` statements required by the used ingredients and with the ingredient +body. + +### Regions +You should use `# ` and `# ` lines to indicate where start and end +of a region should be placed in the generated snippet. Those lines will be simply replaced with the proper +`START region_name` and `END region_name` lines. diff --git a/compute/client_library/__init__.py b/compute/client_library/__init__.py new file mode 100644 index 00000000000..4bbe0ffdb06 --- /dev/null +++ b/compute/client_library/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/compute/client_library/ingredients/__init__.py b/compute/client_library/ingredients/__init__.py new file mode 100644 index 00000000000..81d8b9be3da --- /dev/null +++ b/compute/client_library/ingredients/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa diff --git a/compute/client_library/ingredients/disks/autodelete_change.py b/compute/client_library/ingredients/disks/autodelete_change.py new file mode 100644 index 00000000000..4238c30949c --- /dev/null +++ b/compute/client_library/ingredients/disks/autodelete_change.py @@ -0,0 +1,53 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import sys +from typing import NoReturn + + +from google.cloud import compute_v1 + + +# +def set_disk_autodelete(project_id: str, zone: str, instance_name: str, disk_name: str, autodelete: bool) -> NoReturn: + """ + Set the autodelete flag of a disk to given value. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone in which is the disk you want to modify. + instance_name: name of the instance the disk is attached to. + disk_name: the name of the disk which flag you want to modify. + autodelete: the new value of the autodelete flag. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get(project=project_id, zone=zone, instance=instance_name) + + for disk in instance.disks: + if disk.device_name == disk_name: + break + else: + raise RuntimeError(f"Instance {instance_name} doesn't have a disk named {disk_name} attached.") + + disk.auto_delete = autodelete + + operation = instance_client.update(project=project_id, zone=zone, instance=instance_name, instance_resource=instance) + + wait_for_extended_operation(operation, "disk update") + return +# diff --git a/compute/client_library/ingredients/disks/clone_encrypted_disk.py b/compute/client_library/ingredients/disks/clone_encrypted_disk.py new file mode 100644 index 00000000000..2df729de693 --- /dev/null +++ b/compute/client_library/ingredients/disks/clone_encrypted_disk.py @@ -0,0 +1,64 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +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) +# diff --git a/compute/client_library/ingredients/disks/clone_encrypted_disk_managed_key.py b/compute/client_library/ingredients/disks/clone_encrypted_disk_managed_key.py new file mode 100644 index 00000000000..53ab7baa5a1 --- /dev/null +++ b/compute/client_library/ingredients/disks/clone_encrypted_disk_managed_key.py @@ -0,0 +1,65 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +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. + + 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 + 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) +# diff --git a/compute/client_library/ingredients/disks/create_empty_disk.py b/compute/client_library/ingredients/disks/create_empty_disk.py new file mode 100644 index 00000000000..b06f283f08f --- /dev/null +++ b/compute/client_library/ingredients/disks/create_empty_disk.py @@ -0,0 +1,55 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import sys + +from google.cloud import compute_v1 + + +# +def create_empty_disk( + project_id: str, zone: str, disk_name: str, disk_type: str, disk_size_gb: int +) -> compute_v1.Disk: + """ + Creates a new empty disk in a project in 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 + + Returns: + An unattached Disk instance. + """ + disk = compute_v1.Disk() + disk.size_gb = disk_size_gb + disk.name = disk_name + disk.zone = zone + disk.type_ = disk_type + + disk_client = compute_v1.DisksClient() + 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) +# diff --git a/compute/client_library/ingredients/disks/create_from_image.py b/compute/client_library/ingredients/disks/create_from_image.py new file mode 100644 index 00000000000..ab6ebf28ecc --- /dev/null +++ b/compute/client_library/ingredients/disks/create_from_image.py @@ -0,0 +1,59 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import sys + +from google.cloud import compute_v1 + + +# +def create_disk_from_image( + project_id: str, zone: str, disk_name: str, disk_type: str, disk_size_gb: int, source_image: str +) -> compute_v1.Disk: + """ + Creates a new disk in a project in given zone using an image as base. + + 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 + source_image: source image to use when creating this disk. You must have read access to this disk. This + can be one of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + + Returns: + An unattached Disk instance. + """ + disk = compute_v1.Disk() + disk.size_gb = disk_size_gb + disk.name = disk_name + disk.zone = zone + disk.type_ = disk_type + disk.source_image = source_image + + disk_client = compute_v1.DisksClient() + 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) +# diff --git a/compute/client_library/ingredients/disks/create_from_snapshot.py b/compute/client_library/ingredients/disks/create_from_snapshot.py new file mode 100644 index 00000000000..a2dd1f49595 --- /dev/null +++ b/compute/client_library/ingredients/disks/create_from_snapshot.py @@ -0,0 +1,55 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + + +from google.cloud import compute_v1 + + +# +def create_disk_from_snapshot(project_id: str, zone: str, disk_name: str, disk_type: str, disk_size_gb: int, snapshot_link: str) -> compute_v1.Disk: + """ + Creates a new disk in a project in 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 + 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 unattached Disk instance. + """ + disk_client = compute_v1.DisksClient() + disk = compute_v1.Disk() + disk.zone = zone + disk.size_gb = disk_size_gb + disk.source_snapshot = snapshot_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) +# diff --git a/compute/client_library/ingredients/disks/create_from_source.py b/compute/client_library/ingredients/disks/create_from_source.py new file mode 100644 index 00000000000..a7ed3006ceb --- /dev/null +++ b/compute/client_library/ingredients/disks/create_from_source.py @@ -0,0 +1,55 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +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) +# diff --git a/compute/client_library/ingredients/disks/create_kms_encrypted_disk.py b/compute/client_library/ingredients/disks/create_kms_encrypted_disk.py new file mode 100644 index 00000000000..7078ea6b6b6 --- /dev/null +++ b/compute/client_library/ingredients/disks/create_kms_encrypted_disk.py @@ -0,0 +1,70 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import Optional + +from google.cloud import compute_v1 + + +# +def create_kms_encrypted_disk(project_id: str, zone: str, disk_name: str, disk_type: str, + disk_size_gb: int, kms_key_name: str, + disk_link: Optional[str] = None, image_link: Optional[str] = None) -> compute_v1.Disk: + """ + Creates a zonal disk in a project. If you do not provide values for disk_link or image_link, + an empty disk will be created. + + 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 + 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}" + 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}" + image_link: a link to the image you want to use as a source for the new disk. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + + Returns: + An attachable disk. + """ + disk_client = compute_v1.DisksClient() + disk = compute_v1.Disk() + disk.zone = zone + disk.size_gb = disk_size_gb + if disk_link: + disk.source_disk = disk_link + if image_link: + disk.source_image = image_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 + 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) + +# diff --git a/compute/client_library/ingredients/disks/delete.py b/compute/client_library/ingredients/disks/delete.py new file mode 100644 index 00000000000..6b3788b9415 --- /dev/null +++ b/compute/client_library/ingredients/disks/delete.py @@ -0,0 +1,39 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import sys +from typing import NoReturn + +from google.cloud import compute_v1 + + +# +def delete_disk(project_id: str, zone: str, disk_name: str) -> NoReturn: + """ + Deletes a disk from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone in which is the disk you want to delete. + disk_name: name of the disk you want to delete. + """ + disk_client = compute_v1.DisksClient() + operation = disk_client.delete(project=project_id, zone=zone, disk=disk_name) + wait_for_extended_operation(operation, "disk deletion") + return +# diff --git a/compute/client_library/ingredients/disks/disk_from_snapshot.py b/compute/client_library/ingredients/disks/disk_from_snapshot.py new file mode 100644 index 00000000000..f7abd0c5a68 --- /dev/null +++ b/compute/client_library/ingredients/disks/disk_from_snapshot.py @@ -0,0 +1,54 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def disk_from_snapshot( + disk_type: str, disk_size_gb: int, boot: bool, source_snapshot: str, auto_delete: bool = True +) -> compute_v1.AttachedDisk(): + """ + Create an AttachedDisk object to be used in VM instance creation. Uses a disk snapshot as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_snapshot: disk snapshot to use when creating this disk. You must have read access to this disk. + This value uses the following format: "projects/{project_name}/global/snapshots/{snapshot_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified snapshot. + """ + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_snapshot = source_snapshot + initialize_params.disk_type = disk_type + initialize_params.disk_size_gb = disk_size_gb + disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + disk.auto_delete = auto_delete + disk.boot = boot + return disk +# diff --git a/compute/client_library/ingredients/disks/empty_disk.py b/compute/client_library/ingredients/disks/empty_disk.py new file mode 100644 index 00000000000..70c677b7da3 --- /dev/null +++ b/compute/client_library/ingredients/disks/empty_disk.py @@ -0,0 +1,49 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def empty_disk(disk_type: str, disk_size_gb: int, boot: bool = False, auto_delete: bool = True) -> compute_v1.AttachedDisk(): + """ + Create an AttachedDisk object to be used in VM instance creation. The created disk contains + no data and requires formatting before it can be used. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created as an empty disk. + """ + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.disk_type = disk_type + initialize_params.disk_size_gb = disk_size_gb + disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + disk.auto_delete = auto_delete + disk.boot = boot + return disk +# diff --git a/compute/client_library/ingredients/disks/from_image.py b/compute/client_library/ingredients/disks/from_image.py new file mode 100644 index 00000000000..945b018b9f8 --- /dev/null +++ b/compute/client_library/ingredients/disks/from_image.py @@ -0,0 +1,55 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def disk_from_image( + disk_type: str, disk_size_gb: int, boot: bool, source_image: str, auto_delete: bool = True +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk +# diff --git a/compute/client_library/ingredients/disks/get.py b/compute/client_library/ingredients/disks/get.py new file mode 100644 index 00000000000..54b68d9d4ae --- /dev/null +++ b/compute/client_library/ingredients/disks/get.py @@ -0,0 +1,37 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import sys +from typing import NoReturn, Iterable + +from google.cloud import compute_v1 + + +# +def get_disk(project_id: str, zone: str, disk_name: str) -> compute_v1.Disk: + """ + Gets a disk from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone where the disk exists. + disk_name: name of the disk you want to retrieve. + """ + disk_client = compute_v1.DisksClient() + return disk_client.get(project=project_id, zone=zone, disk=disk_name) +# diff --git a/compute/client_library/ingredients/disks/list.py b/compute/client_library/ingredients/disks/list.py new file mode 100644 index 00000000000..ec3ecab83cc --- /dev/null +++ b/compute/client_library/ingredients/disks/list.py @@ -0,0 +1,43 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import sys +from typing import NoReturn, Iterable + +from google.cloud import compute_v1 + + +# +def list_disks(project_id: str, zone: str, filter_: str = "") -> Iterable[compute_v1.Disk]: + """ + Deletes a disk from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone in which is the disk you want to delete. + filter_: filter to be applied when listing disks. Learn more about filters here: + https://cloud.google.com/python/docs/reference/compute/latest/google.cloud.compute_v1.types.ListDisksRequest + """ + disk_client = compute_v1.DisksClient() + request = compute_v1.ListDisksRequest() + request.project = project_id + request.zone = zone + request.filter = filter_ + return disk_client.list(request) +# + diff --git a/compute/client_library/ingredients/disks/local_ssd.py b/compute/client_library/ingredients/disks/local_ssd.py new file mode 100644 index 00000000000..8b7f6f54d34 --- /dev/null +++ b/compute/client_library/ingredients/disks/local_ssd.py @@ -0,0 +1,41 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def local_ssd_disk(zone: str) -> compute_v1.AttachedDisk(): + """ + Create an AttachedDisk object to be used in VM instance creation. The created disk contains + no data and requires formatting before it can be used. + + Args: + zone: The zone in which the local SSD drive will be attached. + + Returns: + AttachedDisk object configured as a local SSD disk. + """ + disk = compute_v1.AttachedDisk() + disk.type_ = compute_v1.AttachedDisk.Type.SCRATCH.name + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.disk_type = f"zones/{zone}/diskTypes/local-ssd" + disk.initialize_params = initialize_params + disk.auto_delete = True + return disk +# diff --git a/compute/client_library/ingredients/disks/regional_create_from_source.py b/compute/client_library/ingredients/disks/regional_create_from_source.py new file mode 100644 index 00000000000..af6757d7efb --- /dev/null +++ b/compute/client_library/ingredients/disks/regional_create_from_source.py @@ -0,0 +1,68 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import Iterable, Optional + +from google.cloud import compute_v1 + + +# +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: Optional[str] = None, + snapshot_link: Optional[str] = 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) +# diff --git a/compute/client_library/ingredients/disks/regional_delete.py b/compute/client_library/ingredients/disks/regional_delete.py new file mode 100644 index 00000000000..0bc2f59d87a --- /dev/null +++ b/compute/client_library/ingredients/disks/regional_delete.py @@ -0,0 +1,39 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import sys +from typing import NoReturn + +from google.cloud import compute_v1 + + +# +def delete_regional_disk(project_id: str, region: str, disk_name: str) -> NoReturn: + """ + Deletes a disk from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + region:name of the region where the disk is located. + disk_name: name of the disk that you want to delete. + """ + disk_client = compute_v1.RegionDisksClient() + operation = disk_client.delete(project=project_id, region=region, disk=disk_name) + wait_for_extended_operation(operation, "regional disk deletion") + return +# diff --git a/compute/client_library/ingredients/firewall/create.py b/compute/client_library/ingredients/firewall/create.py new file mode 100644 index 00000000000..d6f17b090a4 --- /dev/null +++ b/compute/client_library/ingredients/firewall/create.py @@ -0,0 +1,72 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def create_firewall_rule( + project_id: str, firewall_rule_name: str, network: str = "global/networks/default" +) -> compute_v1.Firewall: + """ + Creates a simple firewall rule allowing for incoming HTTP and HTTPS access from the entire Internet. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + firewall_rule_name: name of the rule that is created. + network: name of the network the rule will be applied to. Available name formats: + * https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network} + * projects/{project_id}/global/networks/{network} + * global/networks/{network} + + Returns: + A Firewall object. + """ + firewall_rule = compute_v1.Firewall() + firewall_rule.name = firewall_rule_name + firewall_rule.direction = "INGRESS" + + allowed_ports = compute_v1.Allowed() + allowed_ports.I_p_protocol = "tcp" + allowed_ports.ports = ["80", "443"] + + firewall_rule.allowed = [allowed_ports] + firewall_rule.source_ranges = ["0.0.0.0/0"] + firewall_rule.network = network + firewall_rule.description = "Allowing TCP traffic on port 80 and 443 from Internet." + + firewall_rule.target_tags = ["web"] + + # Note that the default value of priority for the firewall API is 1000. + # If you check the value of `firewall_rule.priority` at this point it + # will be equal to 0, however it is not treated as "set" by the library and thus + # the default will be applied to the new rule. If you want to create a rule that + # has priority == 0, you need to explicitly set it so: + # TODO: Uncomment to set the priority to 0 + # firewall_rule.priority = 0 + + firewall_client = compute_v1.FirewallsClient() + operation = firewall_client.insert( + project=project_id, firewall_resource=firewall_rule + ) + + wait_for_extended_operation(operation, "firewall rule creation") + + return firewall_client.get(project=project_id, firewall=firewall_rule_name) +# diff --git a/compute/client_library/ingredients/firewall/delete.py b/compute/client_library/ingredients/firewall/delete.py new file mode 100644 index 00000000000..1f4b8703106 --- /dev/null +++ b/compute/client_library/ingredients/firewall/delete.py @@ -0,0 +1,39 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def delete_firewall_rule(project_id: str, firewall_rule_name: str) -> None: + """ + Deletes a firewall rule from the project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + firewall_rule_name: name of the firewall rule you want to delete. + """ + firewall_client = compute_v1.FirewallsClient() + operation = firewall_client.delete( + project=project_id, firewall=firewall_rule_name + ) + + wait_for_extended_operation(operation, "firewall rule deletion") + return +# diff --git a/compute/client_library/ingredients/firewall/get.py b/compute/client_library/ingredients/firewall/get.py new file mode 100644 index 00000000000..0a8388d5699 --- /dev/null +++ b/compute/client_library/ingredients/firewall/get.py @@ -0,0 +1,36 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def get_firewall_rule(project_id: str, firewall_rule_name: str) -> compute_v1.Firewall: + """ + Retrieve a Firewall from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + firewall_rule_name: name of the firewall rule you want to retrieve. + + Returns: + A Firewall object. + """ + firewall_client = compute_v1.FirewallsClient() + return firewall_client.get(project=project_id, firewall=firewall_rule_name) +# diff --git a/compute/client_library/ingredients/firewall/list.py b/compute/client_library/ingredients/firewall/list.py new file mode 100644 index 00000000000..5deeac4e3b7 --- /dev/null +++ b/compute/client_library/ingredients/firewall/list.py @@ -0,0 +1,44 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import Iterable + +from google.cloud import compute_v1 + + +# +def list_firewall_rules(project_id: str) -> Iterable[compute_v1.Firewall]: + """ + Return a list of all the firewall rules in specified project. Also prints the + list of firewall names and their descriptions. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + + Returns: + A flat list of all firewall rules defined for given project. + """ + firewall_client = compute_v1.FirewallsClient() + firewalls_list = firewall_client.list(project=project_id) + + for firewall in firewalls_list: + print(f" - {firewall.name}: {firewall.description}") + + return firewalls_list +# + diff --git a/compute/client_library/ingredients/firewall/patch.py b/compute/client_library/ingredients/firewall/patch.py new file mode 100644 index 00000000000..0c44979cd3b --- /dev/null +++ b/compute/client_library/ingredients/firewall/patch.py @@ -0,0 +1,46 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def patch_firewall_priority(project_id: str, firewall_rule_name: str, priority: int) -> None: + """ + Modifies the priority of a given firewall rule. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + firewall_rule_name: name of the rule you want to modify. + priority: the new priority to be set for the rule. + """ + firewall_rule = compute_v1.Firewall() + firewall_rule.priority = priority + + # The patch operation doesn't require the full definition of a Firewall object. It will only update + # the values that were set in it, in this case it will only change the priority. + firewall_client = compute_v1.FirewallsClient() + operation = firewall_client.patch( + project=project_id, firewall=firewall_rule_name, firewall_resource=firewall_rule + ) + + wait_for_extended_operation(operation, "firewall rule patching") + return +# + diff --git a/compute/client_library/ingredients/firewall/windows_kms.py b/compute/client_library/ingredients/firewall/windows_kms.py new file mode 100644 index 00000000000..265d86e666f --- /dev/null +++ b/compute/client_library/ingredients/firewall/windows_kms.py @@ -0,0 +1,62 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def create_firewall_rule_for_windows_activation_host( + project_id: str, firewall_rule_name: str, network: str = "global/networks/default" +) -> compute_v1.Firewall: + """ + Creates an egress firewall rule with the highest priority for host + kms.windows.googlecloud.com (35.190.247.13) for Windows activation. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + firewall_rule_name: name of the rule that is created. + network: name of the network the rule will be applied to. Available name formats: + * https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network} + * projects/{project_id}/global/networks/{network} + * global/networks/{network} + + Returns: + A Firewall object. + """ + firewall_rule = compute_v1.Firewall() + firewall_rule.name = firewall_rule_name + firewall_rule.network = network + + allowed = compute_v1.Allowed() + allowed.ports = ['1688'] + allowed.I_p_protocol = 'tcp' + + firewall_rule.allowed = [allowed] + firewall_rule.destination_ranges = ["35.190.247.13/32"] + firewall_rule.direction = compute_v1.Firewall.Direction.EGRESS.name + firewall_rule.priority = 0 + + firewall_client = compute_v1.FirewallsClient() + operation = firewall_client.insert(project=project_id, firewall_resource=firewall_rule) + + wait_for_extended_operation(operation, "windows KSM firewall rule creation") + + return firewall_client.get(project=project_id, firewall=firewall_rule_name) +# + diff --git a/compute/client_library/ingredients/images/create.py b/compute/client_library/ingredients/images/create.py new file mode 100644 index 00000000000..b029d7aaa3c --- /dev/null +++ b/compute/client_library/ingredients/images/create.py @@ -0,0 +1,87 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +import warnings +from typing import Optional + +from google.cloud import compute_v1 + +# +STOPPED_MACHINE_STATUS = ( + compute_v1.Instance.Status.TERMINATED.name, + compute_v1.Instance.Status.STOPPED.name +) + + +def create_image_from_disk(project_id: str, zone: str, source_disk_name: str, image_name: str, + storage_location: Optional[str] = None, force_create: bool = False) -> compute_v1.Image: + """ + Creates a new disk image. + + Args: + project_id: project ID or project number of the Cloud project you use. + zone: zone of the disk you copy from. + source_disk_name: name of the source disk you copy from. + image_name: name of the image you want to create. + storage_location: storage location for the image. If the value is undefined, + function will store the image in the multi-region closest to your image's + source location. + force_create: create the image even if the source disk is attached to a + running instance. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + disk_client = compute_v1.DisksClient() + instance_client = compute_v1.InstancesClient() + + # Get source disk + disk = disk_client.get(project=project_id, zone=zone, disk=source_disk_name) + + for disk_user in disk.users: + instance = instance_client.get(project=project_id, zone=zone, instance=disk_user) + if instance.status in STOPPED_MACHINE_STATUS: + continue + if not force_create: + raise RuntimeError(f"Instance {disk_user} should be stopped. For Windows instances please " + f"stop the instance using `GCESysprep` command. For Linux instances just " + f"shut it down normally. You can supress this error and create an image of" + f"the disk by setting `force_create` parameter to true (not recommended). \n" + f"More information here: \n" + f" * https://cloud.google.com/compute/docs/instances/windows/creating-windows-os-image#api \n" + f" * https://cloud.google.com/compute/docs/images/create-delete-deprecate-private-images#prepare_instance_for_image") + else: + warnings.warn(f"Warning: The `force_create` option may compromise the integrity of your image. " + f"Stop the {disk_user} instance before you create the image if possible.") + + # Create image + image = compute_v1.Image() + image.source_disk = disk.self_link + image.name = image_name + if storage_location: + image.storage_locations = [storage_location] + + operation = image_client.insert(project=project_id, image_resource=image) + + wait_for_extended_operation(operation, "image creation from disk") + + return image_client.get(project=project_id, image=image_name) +# diff --git a/compute/client_library/ingredients/images/create_from_image.py b/compute/client_library/ingredients/images/create_from_image.py new file mode 100644 index 00000000000..07b6d1c8e76 --- /dev/null +++ b/compute/client_library/ingredients/images/create_from_image.py @@ -0,0 +1,69 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import Optional, Iterable + +from google.cloud import compute_v1 + + +# +def create_image_from_image(project_id: str, source_image_name: str, image_name: str, + source_project_id: Optional[str] = None, + guest_os_features: Optional[Iterable[str]] = None, + storage_location: Optional[str] = None) -> compute_v1.Image: + """ + Creates a copy of another image. + + Args: + project_id: project ID or project number of the Cloud project you want to place your new image in. + source_image_name: name of the image you want to copy. + image_name: name of the image you want to create. + source_project_id: name of the project that hosts the source image. If left unset, it's assumed to equal + the `project_id`. + guest_os_features: an iterable collection of guest features you want to enable for the bootable image. + Learn more about Guest OS features here: + https://cloud.google.com/compute/docs/images/create-delete-deprecate-private-images#guest-os-features + storage_location: the storage location of your image. For example, specify "us" to store the image in the + `us` multi-region, or "us-central1" to store it in the `us-central1` region. If you do not make a selection, + Compute Engine stores the image in the multi-region closest to your image's source location. + + Returns: + An Image object. + """ + if source_project_id is None: + source_project_id = project_id + + image_client = compute_v1.ImagesClient() + src_image = image_client.get(project=source_project_id, image=source_image_name) + + image = compute_v1.Image() + image.name = image_name + image.source_image = src_image.self_link + if storage_location: + image.storage_locations = [storage_location] + + if guest_os_features: + image.guest_os_features = [compute_v1.GuestOsFeature(type_=feature) for feature in guest_os_features] + + operation = image_client.insert(project=project_id, image_resource=image) + + wait_for_extended_operation(operation, "image creation from image") + + return image_client.get(project=project_id, image=image_name) +# diff --git a/compute/client_library/ingredients/images/create_from_snapshot.py b/compute/client_library/ingredients/images/create_from_snapshot.py new file mode 100644 index 00000000000..f882e24914b --- /dev/null +++ b/compute/client_library/ingredients/images/create_from_snapshot.py @@ -0,0 +1,71 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import Optional, Iterable + +from google.cloud import compute_v1 + + +# +def create_image_from_snapshot(project_id: str, source_snapshot_name: str, image_name: str, + source_project_id: Optional[str] = None, + guest_os_features: Optional[Iterable[str]] = None, + storage_location: Optional[str] = None) -> compute_v1.Image: + """ + Creates an image based on a snapshot. + + Args: + project_id: project ID or project number of the Cloud project you want to place your new image in. + source_snapshot_name: name of the snapshot you want to use as a base of your image. + image_name: name of the image you want to create. + source_project_id: name of the project that hosts the source snapshot. If left unset, it's assumed to equal + the `project_id`. + guest_os_features: an iterable collection of guest features you want to enable for the bootable image. + Learn more about Guest OS features here: + https://cloud.google.com/compute/docs/images/create-delete-deprecate-private-images#guest-os-features + storage_location: the storage location of your image. For example, specify "us" to store the image in the + `us` multi-region, or "us-central1" to store it in the `us-central1` region. If you do not make a selection, + Compute Engine stores the image in the multi-region closest to your image's source location. + + Returns: + An Image object. + """ + if source_project_id is None: + source_project_id = project_id + + snapshot_client = compute_v1.SnapshotsClient() + image_client = compute_v1.ImagesClient() + src_snapshot = snapshot_client.get(project=source_project_id, snapshot=source_snapshot_name) + + image = compute_v1.Image() + image.name = image_name + image.source_snapshot = src_snapshot.self_link + + if storage_location: + image.storage_locations = [storage_location] + + if guest_os_features: + image.guest_os_features = [compute_v1.GuestOsFeature(type_=feature) for feature in guest_os_features] + + operation = image_client.insert(project=project_id, image_resource=image) + + wait_for_extended_operation(operation, "image creation from snapshot") + + return image_client.get(project=project_id, image=image_name) +# diff --git a/compute/client_library/ingredients/images/delete.py b/compute/client_library/ingredients/images/delete.py new file mode 100644 index 00000000000..18059fd5348 --- /dev/null +++ b/compute/client_library/ingredients/images/delete.py @@ -0,0 +1,37 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import NoReturn + +from google.cloud import compute_v1 + + +# +def delete_image(project_id: str, image_name: str) -> NoReturn: + """ + Deletes a disk image. + + Args: + project_id: project ID or project number of the Cloud project you use. + image_name: name of the image you want to delete. + """ + image_client = compute_v1.ImagesClient() + operation = image_client.delete(project=project_id, image=image_name) + wait_for_extended_operation(operation, "image deletion") +# diff --git a/compute/client_library/ingredients/images/get_image.py b/compute/client_library/ingredients/images/get_image.py new file mode 100644 index 00000000000..4dcce0e77c0 --- /dev/null +++ b/compute/client_library/ingredients/images/get_image.py @@ -0,0 +1,37 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def get_image(project_id: str, image_name: str) -> compute_v1.Image: + """ + Retrieve detailed information about a single image from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to list images from. + image_name: name of the image you want to get details of. + + Returns: + An instance of compute_v1.Image object with information about specified image. + """ + image_client = compute_v1.ImagesClient() + return image_client.get(project=project_id, image=image_name) +# diff --git a/compute/client_library/ingredients/images/get_image_from_family.py b/compute/client_library/ingredients/images/get_image_from_family.py new file mode 100644 index 00000000000..45daec115fe --- /dev/null +++ b/compute/client_library/ingredients/images/get_image_from_family.py @@ -0,0 +1,42 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + + +from google.cloud import compute_v1 + + +# +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family( + project=project, family=family + ) + return newest_image +# diff --git a/compute/client_library/ingredients/images/list_images.py b/compute/client_library/ingredients/images/list_images.py new file mode 100644 index 00000000000..b4c191fc3e2 --- /dev/null +++ b/compute/client_library/ingredients/images/list_images.py @@ -0,0 +1,37 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import Iterable + +from google.cloud import compute_v1 + + +# +def list_images(project_id: str) -> Iterable[compute_v1.Image]: + """ + Retrieve a list of images available in given project. + + Args: + project_id: project ID or project number of the Cloud project you want to list images from. + + Returns: + An iterable collection of compute_v1.Image objects. + """ + image_client = compute_v1.ImagesClient() + return image_client.list(project=project_id) +# diff --git a/compute/client_library/ingredients/images/set_depracation_status.py b/compute/client_library/ingredients/images/set_depracation_status.py new file mode 100644 index 00000000000..a817a03e63e --- /dev/null +++ b/compute/client_library/ingredients/images/set_depracation_status.py @@ -0,0 +1,46 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import NoReturn + +from google.cloud import compute_v1 + + +# +def set_deprecation_status(project_id: str, image_name: str, status: compute_v1.DeprecationStatus.State) -> NoReturn: + """ + Modify the deprecation status of an image. + + Note: Image objects by default don't have the `deprecated` attribute at all unless it's set. + + Args: + project_id: project ID or project number of the Cloud project that hosts the image. + image_name: name of the image you want to modify + status: the status you want to set for the image. Available values are available in + `compute_v1.DeprecationStatus.State` enum. Learn more about image deprecation statuses: + https://cloud.google.com/compute/docs/images/create-delete-deprecate-private-images#deprecation-states + """ + image_client = compute_v1.ImagesClient() + deprecation_status = compute_v1.DeprecationStatus() + deprecation_status.state = status.name + operation = image_client.deprecate(project=project_id, image=image_name, + deprecation_status_resource=deprecation_status) + + wait_for_extended_operation(operation, "changing deprecation state of an image") +# diff --git a/compute/client_library/ingredients/instance-templates/create.py b/compute/client_library/ingredients/instance-templates/create.py new file mode 100644 index 00000000000..9c79f8c76b7 --- /dev/null +++ b/compute/client_library/ingredients/instance-templates/create.py @@ -0,0 +1,75 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import sys + +from google.cloud import compute_v1 + + +# +def create_template(project_id: str, template_name: str) -> compute_v1.InstanceTemplate: + """ + Create a new instance template with the provided name and a specific + instance configuration. + + Args: + project_id: project ID or project number of the Cloud project you use. + template_name: name of the new template to create. + + Returns: + InstanceTemplate object that represents the new instance template. + """ + # The template describes the size and source image of the boot disk + # to attach to the instance. + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = ( + "projects/debian-cloud/global/images/family/debian-11" + ) + initialize_params.disk_size_gb = 250 + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + + # The template connects the instance to the `default` network, + # without specifying a subnetwork. + network_interface = compute_v1.NetworkInterface() + network_interface.name = "global/networks/default" + + # The template lets the instance use an external IP address. + access_config = compute_v1.AccessConfig() + access_config.name = "External NAT" + access_config.type_ = "ONE_TO_ONE_NAT" + access_config.network_tier = "PREMIUM" + network_interface.access_configs = [access_config] + + template = compute_v1.InstanceTemplate() + template.name = template_name + template.properties.disks = [disk] + template.properties.machine_type = "e2-standard-4" + template.properties.network_interfaces = [network_interface] + + template_client = compute_v1.InstanceTemplatesClient() + operation = template_client.insert( + project=project_id, instance_template_resource=template + ) + + wait_for_extended_operation(operation, "instance template creation") + + return template_client.get(project=project_id, instance_template=template_name) +# diff --git a/compute/client_library/ingredients/instance-templates/create_from_instance.py b/compute/client_library/ingredients/instance-templates/create_from_instance.py new file mode 100644 index 00000000000..1450cf02f21 --- /dev/null +++ b/compute/client_library/ingredients/instance-templates/create_from_instance.py @@ -0,0 +1,64 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def create_template_from_instance( + project_id: str, instance: str, template_name: str +) -> compute_v1.InstanceTemplate: + """ + Create a new instance template based on an existing instance. + This new template specifies a different boot disk. + + Args: + project_id: project ID or project number of the Cloud project you use. + instance: the instance to base the new template on. This value uses + the following format: "projects/{project}/zones/{zone}/instances/{instance_name}" + template_name: name of the new template to create. + + Returns: + InstanceTemplate object that represents the new instance template. + """ + disk = compute_v1.DiskInstantiationConfig() + # Device name must match the name of a disk attached to the instance you are + # basing your template on. + disk.device_name = "disk-1" + # Replace the original boot disk image used in your instance with a Rocky Linux image. + disk.instantiate_from = "CUSTOM_IMAGE" + disk.custom_image = "projects/rocky-linux-cloud/global/images/family/rocky-linux-8" + # Override the auto_delete setting. + disk.auto_delete = True + + template = compute_v1.InstanceTemplate() + template.name = template_name + template.source_instance = instance + template.source_instance_params = compute_v1.SourceInstanceParams() + template.source_instance_params.disk_configs = [disk] + + template_client = compute_v1.InstanceTemplatesClient() + operation = template_client.insert( + project=project_id, instance_template_resource=template + ) + + wait_for_extended_operation(operation, "instance template creation") + + return template_client.get(project=project_id, instance_template=template_name) +# diff --git a/compute/client_library/ingredients/instance-templates/create_with_subnet.py b/compute/client_library/ingredients/instance-templates/create_with_subnet.py new file mode 100644 index 00000000000..fd91bb3c977 --- /dev/null +++ b/compute/client_library/ingredients/instance-templates/create_with_subnet.py @@ -0,0 +1,72 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def create_template_with_subnet( + project_id: str, network: str, subnetwork: str, template_name: str +) -> compute_v1.InstanceTemplate: + """ + Create an instance template that uses a provided subnet. + + Args: + project_id: project ID or project number of the Cloud project you use. + network: the network to be used in the new template. This value uses + the following format: "projects/{project}/global/networks/{network}" + subnetwork: the subnetwork to be used in the new template. This value + uses the following format: "projects/{project}/regions/{region}/subnetworks/{subnetwork}" + template_name: name of the new template to create. + + Returns: + InstanceTemplate object that represents the new instance template. + """ + # The template describes the size and source image of the book disk to + # attach to the instance. + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = ( + "projects/debian-cloud/global/images/family/debian-11" + ) + initialize_params.disk_size_gb = 250 + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + + template = compute_v1.InstanceTemplate() + template.name = template_name + template.properties = compute_v1.InstanceProperties() + template.properties.disks = [disk] + template.properties.machine_type = "e2-standard-4" + + # The template connects the instance to the specified network and subnetwork. + network_interface = compute_v1.NetworkInterface() + network_interface.network = network + network_interface.subnetwork = subnetwork + template.properties.network_interfaces = [network_interface] + + template_client = compute_v1.InstanceTemplatesClient() + operation = template_client.insert( + project=project_id, instance_template_resource=template + ) + wait_for_extended_operation(operation, "instance template creation") + + return template_client.get(project=project_id, instance_template=template_name) +# diff --git a/compute/client_library/ingredients/instance-templates/delete.py b/compute/client_library/ingredients/instance-templates/delete.py new file mode 100644 index 00000000000..f3720666026 --- /dev/null +++ b/compute/client_library/ingredients/instance-templates/delete.py @@ -0,0 +1,38 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def delete_instance_template(project_id: str, template_name: str): + """ + Delete an instance template. + + Args: + project_id: project ID or project number of the Cloud project you use. + template_name: name of the template to delete. + """ + template_client = compute_v1.InstanceTemplatesClient() + operation = template_client.delete( + project=project_id, instance_template=template_name + ) + wait_for_extended_operation(operation, "instance template deletion") + return +# diff --git a/compute/client_library/ingredients/instance-templates/get.py b/compute/client_library/ingredients/instance-templates/get.py new file mode 100644 index 00000000000..99aae684df0 --- /dev/null +++ b/compute/client_library/ingredients/instance-templates/get.py @@ -0,0 +1,40 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def get_instance_template( + project_id: str, template_name: str +) -> compute_v1.InstanceTemplate: + """ + Retrieve an instance template, which you can use to create virtual machine + (VM) instances and managed instance groups (MIGs). + + Args: + project_id: project ID or project number of the Cloud project you use. + template_name: name of the template to retrieve. + + Returns: + InstanceTemplate object that represents the retrieved template. + """ + template_client = compute_v1.InstanceTemplatesClient() + return template_client.get(project=project_id, instance_template=template_name) +# diff --git a/compute/client_library/ingredients/instance-templates/list.py b/compute/client_library/ingredients/instance-templates/list.py new file mode 100644 index 00000000000..851e2c48e50 --- /dev/null +++ b/compute/client_library/ingredients/instance-templates/list.py @@ -0,0 +1,37 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from typing import Iterable +from google.cloud import compute_v1 + + +# +def list_instance_templates(project_id: str) -> Iterable[compute_v1.InstanceTemplate]: + """ + Get a list of InstanceTemplate objects available in a project. + + Args: + project_id: project ID or project number of the Cloud project you use. + + Returns: + Iterable list of InstanceTemplate objects. + """ + template_client = compute_v1.InstanceTemplatesClient() + return template_client.list(project=project_id) +# diff --git a/compute/client_library/ingredients/instances/__init__.py b/compute/client_library/ingredients/instances/__init__.py new file mode 100644 index 00000000000..81d8b9be3da --- /dev/null +++ b/compute/client_library/ingredients/instances/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa diff --git a/compute/client_library/ingredients/instances/bulk_insert.py b/compute/client_library/ingredients/instances/bulk_insert.py new file mode 100644 index 00000000000..d7cf578d41a --- /dev/null +++ b/compute/client_library/ingredients/instances/bulk_insert.py @@ -0,0 +1,90 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa +from typing import Iterable, Optional +import uuid + +from google.cloud import compute_v1 + + +# +def bulk_insert_instance(project_id: str, zone: str, template: compute_v1.InstanceTemplate, + count: int, name_pattern: str, min_count: Optional[int] = None, + labels: Optional[dict] = None) -> Iterable[compute_v1.Instance]: + """ + Create multiple VMs based on an Instance Template. The newly created instances will + be returned as a list and will share a label with key `bulk_batch` and a random + value. + + If the bulk insert operation fails and the requested number of instances can't be created, + and more than min_count instances are created, then those instances can be found using + the `bulk_batch` label with value attached to the raised exception in bulk_batch_id + attribute. So, you can use the following filter: f"label.bulk_batch={err.bulk_batch_id}" + when listing instances in a zone to get the instances that were successfully created. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + template: an Instance Template to be used for creation of the new VMs. + name_pattern: The string pattern used for the names of the VMs. The pattern + must contain one continuous sequence of placeholder hash characters (#) + with each character corresponding to one digit of the generated instance + name. Example: a name_pattern of inst-#### generates instance names such + as inst-0001 and inst-0002. If existing instances in the same project and + zone have names that match the name pattern then the generated instance + numbers start after the biggest existing number. For example, if there + exists an instance with name inst-0050, then instance names generated + using the pattern inst-#### begin with inst-0051. The name pattern + placeholder #...# can contain up to 18 characters. + count: The maximum number of instances to create. + min_count (optional): The minimum number of instances to create. If no min_count is + specified then count is used as the default value. If min_count instances + cannot be created, then no instances will be created and instances already + created will be deleted. + labels (optional): A dictionary with labels to be added to the new VMs. + """ + bulk_insert_resource = compute_v1.BulkInsertInstanceResource() + bulk_insert_resource.source_instance_template = template.self_link + bulk_insert_resource.count = count + bulk_insert_resource.min_count = min_count or count + bulk_insert_resource.name_pattern = name_pattern + + if not labels: + labels = {} + + labels['bulk_batch'] = uuid.uuid4().hex + instance_prop = compute_v1.InstanceProperties() + instance_prop.labels = labels + bulk_insert_resource.instance_properties = instance_prop + + bulk_insert_request = compute_v1.BulkInsertInstanceRequest() + bulk_insert_request.bulk_insert_instance_resource_resource = bulk_insert_resource + bulk_insert_request.project = project_id + bulk_insert_request.zone = zone + + client = compute_v1.InstancesClient() + operation = client.bulk_insert(bulk_insert_request) + + try: + wait_for_extended_operation(operation, "bulk instance creation") + except Exception as err: + err.bulk_batch_id = labels['bulk_batch'] + raise err + + list_req = compute_v1.ListInstancesRequest() + list_req.project = project_id + list_req.zone = zone + list_req.filter = " AND ".join(f"labels.{key}:{value}" for key, value in labels.items()) + return client.list(list_req) +# diff --git a/compute/client_library/ingredients/instances/create_instance.py b/compute/client_library/ingredients/instances/create_instance.py new file mode 100644 index 00000000000..afbbbef330a --- /dev/null +++ b/compute/client_library/ingredients/instances/create_instance.py @@ -0,0 +1,155 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +import re +from typing import List +import warnings + +from google.cloud import compute_v1 + + +# +def create_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn("Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = compute_v1.Scheduling.ProvisioningModel.SPOT.name + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) +# diff --git a/compute/client_library/ingredients/instances/create_instance_from_template.py b/compute/client_library/ingredients/instances/create_instance_from_template.py new file mode 100644 index 00000000000..b007f4f4023 --- /dev/null +++ b/compute/client_library/ingredients/instances/create_instance_from_template.py @@ -0,0 +1,56 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def create_instance_from_template( + project_id: str, zone: str, instance_name: str, instance_template_url: str +) -> compute_v1.Instance: + """ + Creates a Compute Engine VM instance from an instance template. + + Args: + project_id: ID or number of the project you want to use. + zone: Name of the zone you want to check, for example: us-west3-b + instance_name: Name of the new instance. + instance_template_url: URL of the instance template used for creating the new instance. + It can be a full or partial URL. + Examples: + - https://www.googleapis.com/compute/v1/projects/project/global/instanceTemplates/example-instance-template + - projects/project/global/instanceTemplates/example-instance-template + - global/instanceTemplates/example-instance-template + + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + instance_insert_request = compute_v1.InsertInstanceRequest() + instance_insert_request.project = project_id + instance_insert_request.zone = zone + instance_insert_request.source_instance_template = instance_template_url + instance_insert_request.instance_resource.name = instance_name + + operation = instance_client.insert(instance_insert_request) + wait_for_extended_operation(operation, "instance creation") + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) +# diff --git a/compute/client_library/ingredients/instances/create_instance_from_template_with_overrides.py b/compute/client_library/ingredients/instances/create_instance_from_template_with_overrides.py new file mode 100644 index 00000000000..39432712b92 --- /dev/null +++ b/compute/client_library/ingredients/instances/create_instance_from_template_with_overrides.py @@ -0,0 +1,96 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def create_instance_from_template_with_overrides( + project_id: str, + zone: str, + instance_name: str, + instance_template_name: str, + machine_type: str, + new_disk_source_image: str, +) -> compute_v1.Instance: + """ + Creates a Compute Engine VM instance from an instance template, changing the machine type and + adding a new disk created from a source image. + + Args: + project_id: ID or number of the project you want to use. + zone: Name of the zone you want to check, for example: us-west3-b + instance_name: Name of the new instance. + instance_template_name: Name of the instance template used for creating the new instance. + machine_type: Machine type you want to set in following format: + "zones/{zone}/machineTypes/{type_name}". For example: + - "zones/europe-west3-c/machineTypes/f1-micro" + - You can find the list of available machine types using: + https://cloud.google.com/sdk/gcloud/reference/compute/machine-types/list + new_disk_source_image: Path the the disk image you want to use for your new + disk. This can be one of the public images + (like "projects/debian-cloud/global/images/family/debian-10") + or a private image you have access to. + For a list of available public images, see the documentation: + http://cloud.google.com/compute/docs/images + + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + instance_template_client = compute_v1.InstanceTemplatesClient() + + # Retrieve an instance template by name. + instance_template = instance_template_client.get( + project=project_id, instance_template=instance_template_name + ) + + # Adjust diskType field of the instance template to use the URL formatting required by instances.insert.diskType + # For instance template, there is only a name, not URL. + for disk in instance_template.properties.disks: + if disk.initialize_params.disk_type: + disk.initialize_params.disk_type = ( + f"zones/{zone}/diskTypes/{disk.initialize_params.disk_type}" + ) + + instance = compute_v1.Instance() + instance.name = instance_name + instance.machine_type = machine_type + instance.disks = list(instance_template.properties.disks) + + new_disk = compute_v1.AttachedDisk() + new_disk.initialize_params.disk_size_gb = 50 + new_disk.initialize_params.source_image = new_disk_source_image + new_disk.auto_delete = True + new_disk.boot = False + new_disk.type_ = "PERSISTENT" + + instance.disks.append(new_disk) + + instance_insert_request = compute_v1.InsertInstanceRequest() + instance_insert_request.project = project_id + instance_insert_request.zone = zone + instance_insert_request.instance_resource = instance + instance_insert_request.source_instance_template = instance_template.self_link + + operation = instance_client.insert(instance_insert_request) + wait_for_extended_operation(operation, "instance creation") + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) +# \ No newline at end of file diff --git a/compute/client_library/ingredients/instances/create_start_instance/create_from_custom_image.py b/compute/client_library/ingredients/instances/create_start_instance/create_from_custom_image.py new file mode 100644 index 00000000000..f3b9bd58ec7 --- /dev/null +++ b/compute/client_library/ingredients/instances/create_start_instance/create_from_custom_image.py @@ -0,0 +1,44 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def create_from_custom_image( + project_id: str, zone: str, instance_name: str, custom_image_link: str +) -> compute_v1.Instance: + """ + Create a new VM instance with custom image used as its boot disk. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + custom_image_link: link to the custom image you want to use in the form of: + "projects/{project_name}/global/images/{image_name}" + + Returns: + Instance object. + """ + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, custom_image_link, True)] + instance = create_instance(project_id, zone, instance_name, disks) + return instance +# diff --git a/compute/client_library/ingredients/instances/create_start_instance/create_from_public_image.py b/compute/client_library/ingredients/instances/create_start_instance/create_from_public_image.py new file mode 100644 index 00000000000..fdf2e35f748 --- /dev/null +++ b/compute/client_library/ingredients/instances/create_start_instance/create_from_public_image.py @@ -0,0 +1,42 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + +# +def create_from_public_image(project_id: str, zone: str, instance_name: str) -> compute_v1.Instance: + """ + Create a new VM instance with Debian 10 operating system. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-10" + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link, True)] + instance = create_instance(project_id, zone, instance_name, disks) + return instance +# diff --git a/compute/client_library/ingredients/instances/create_start_instance/create_from_snapshot.py b/compute/client_library/ingredients/instances/create_start_instance/create_from_snapshot.py new file mode 100644 index 00000000000..a2729332cf1 --- /dev/null +++ b/compute/client_library/ingredients/instances/create_start_instance/create_from_snapshot.py @@ -0,0 +1,43 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + + +# +def create_from_snapshot( + project_id: str, zone: str, instance_name: str, snapshot_link: str +): + """ + Create a new VM instance with boot disk created from a snapshot. The + new boot disk will have 20 gigabytes. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + snapshot_link: link to the snapshot you want to use as the source of your + boot disk in the form of: "projects/{project_name}/global/snapshots/{snapshot_name}" + + Returns: + Instance object. + """ + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_snapshot(disk_type, 20, True, snapshot_link)] + instance = create_instance(project_id, zone, instance_name, disks) + return instance +# diff --git a/compute/client_library/ingredients/instances/create_start_instance/create_windows_instance.py b/compute/client_library/ingredients/instances/create_start_instance/create_windows_instance.py new file mode 100644 index 00000000000..ee437e5c562 --- /dev/null +++ b/compute/client_library/ingredients/instances/create_start_instance/create_windows_instance.py @@ -0,0 +1,81 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import Optional + +from google.cloud import compute_v1 + + +# +def create_windows_instance(project_id: str, zone: str, instance_name: str, + machine_type: str, source_image_family: str = "windows-2022", + network_link: str = "global/networks/default", + subnetwork_link: Optional[str] = None) -> compute_v1.Instance: + """ + Creates a new Windows Server instance that has only an internal IP address. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + machine_type: machine type you want to create in following format: + "zones/{zone}/machineTypes/{type_name}". For example: + "zones/europe-west3-c/machineTypes/f1-micro" + You can find the list of available machine types using: + https://cloud.google.com/sdk/gcloud/reference/compute/machine-types/list + source_image_family: name of the public image family for Windows Server or SQL Server images. + https://cloud.google.com/compute/docs/images#os-compute-support + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + + Returns: + Instance object. + """ + if subnetwork_link is None: + subnetwork_link = f'regions/{zone}/subnetworks/default' + + base_image = get_image_from_family( + project="windows-cloud", family=source_image_family + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 100, True, base_image.self_link, True)] + + # You must verify or configure routes and firewall rules in your VPC network + # to allow access to kms.windows.googlecloud.com. + # More information about access to kms.windows.googlecloud.com: https://cloud.google.com/compute/docs/instances/windows/creating-managing-windows-instances#kms-server + + # Additionally, you must enable Private Google Access for subnets in your VPC network + # that contain Windows instances with only internal IP addresses. + # More information about Private Google Access: https://cloud.google.com/vpc/docs/configure-private-google-access#enabling + + instance = create_instance( + project_id, + zone, + instance_name, + disks, + machine_type=machine_type, + network_link=network_link, + subnetwork_link=subnetwork_link, + external_access=True, # Set this to False to disable external IP for your instance + ) + return instance +# diff --git a/compute/client_library/ingredients/instances/create_start_instance/create_with_additional_disk.py b/compute/client_library/ingredients/instances/create_start_instance/create_with_additional_disk.py new file mode 100644 index 00000000000..b921f27a3a0 --- /dev/null +++ b/compute/client_library/ingredients/instances/create_start_instance/create_with_additional_disk.py @@ -0,0 +1,46 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def create_with_additional_disk(project_id: str, zone: str, instance_name: str) -> compute_v1.Instance: + """ + Create a new VM instance with Debian 10 operating system on a 20 GB disk + and a 25 GB additional empty disk. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-10" + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [ + disk_from_image(disk_type, 20, True, newest_debian.self_link), + empty_disk(disk_type, 25), + ] + instance = create_instance(project_id, zone, instance_name, disks) + return instance +# \ No newline at end of file diff --git a/compute/client_library/ingredients/instances/create_start_instance/create_with_existing_disks.py b/compute/client_library/ingredients/instances/create_start_instance/create_with_existing_disks.py new file mode 100644 index 00000000000..c4b90a62f93 --- /dev/null +++ b/compute/client_library/ingredients/instances/create_start_instance/create_with_existing_disks.py @@ -0,0 +1,50 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import List + +from google.cloud import compute_v1 + + +# +def create_with_existing_disks(project_id: str, zone: str, instance_name: str, disk_names: List[str]) -> compute_v1.Instance: + """ + Create a new VM instance using selected disks. The first disk in disk_names will + be used as boot disk. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disk_names: list of disk names to be attached to the new virtual machine. + First disk in this list will be used as the boot device. + + Returns: + Instance object. + """ + assert len(disk_names) >= 1 + disks = [get_disk(project_id, zone, disk_name) for disk_name in disk_names] + attached_disks = [] + for disk in disks: + adisk = compute_v1.AttachedDisk() + adisk.source = disk.self_link + attached_disks.append(adisk) + attached_disks[0].boot = True + instance = create_instance(project_id, zone, instance_name, attached_disks) + return instance +# diff --git a/compute/client_library/ingredients/instances/create_start_instance/create_with_local_ssd.py b/compute/client_library/ingredients/instances/create_start_instance/create_with_local_ssd.py new file mode 100644 index 00000000000..9802a1552d1 --- /dev/null +++ b/compute/client_library/ingredients/instances/create_start_instance/create_with_local_ssd.py @@ -0,0 +1,43 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + +# +def create_with_ssd(project_id: str, zone: str, instance_name: str) -> compute_v1.Instance: + """ + Create a new VM instance with Debian 10 operating system and SSD local disk. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-10" + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link, True), + local_ssd_disk(zone)] + instance = create_instance(project_id, zone, instance_name, disks) + return instance +# diff --git a/compute/client_library/ingredients/instances/create_start_instance/create_with_snapshotted_data_disk.py b/compute/client_library/ingredients/instances/create_start_instance/create_with_snapshotted_data_disk.py new file mode 100644 index 00000000000..ea87201e5f1 --- /dev/null +++ b/compute/client_library/ingredients/instances/create_start_instance/create_with_snapshotted_data_disk.py @@ -0,0 +1,48 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + + +# +def create_with_snapshotted_data_disk( + project_id: str, zone: str, instance_name: str, snapshot_link: str +): + """ + Create a new VM instance with Debian 10 operating system and data disk created from snapshot. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + snapshot_link: link to the snapshot you want to use as the source of your + data disk in the form of: "projects/{project_name}/global/snapshots/{snapshot_name}" + + Returns: + Instance object. + """ + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-10" + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [ + disk_from_image(disk_type, 10, True, newest_debian.self_link), + disk_from_snapshot(disk_type, 11, False, snapshot_link), + ] + instance = create_instance(project_id, zone, instance_name, disks) + return instance +# diff --git a/compute/client_library/ingredients/instances/create_with_subnet.py b/compute/client_library/ingredients/instances/create_with_subnet.py new file mode 100644 index 00000000000..bc39ff2231a --- /dev/null +++ b/compute/client_library/ingredients/instances/create_with_subnet.py @@ -0,0 +1,57 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def create_with_subnet( + project_id: str, zone: str, instance_name: str, network_link: str, subnet_link: str +) -> compute_v1.Instance: + """ + Create a new VM instance with Debian 10 operating system in specified network and subnetwork. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + + Returns: + Instance object. + """ + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-10" + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + instance = create_instance( + project_id, + zone, + instance_name, + disks, + network_link=network_link, + subnetwork_link=subnet_link, + ) + return instance +# diff --git a/compute/client_library/ingredients/instances/custom_hostname/create.py b/compute/client_library/ingredients/instances/custom_hostname/create.py new file mode 100644 index 00000000000..9a990cb9fa4 --- /dev/null +++ b/compute/client_library/ingredients/instances/custom_hostname/create.py @@ -0,0 +1,43 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def create_instance_custom_hostname(project_id: str, zone: str, instance_name: str, hostname: str) -> compute_v1.Instance: + """ + Create a new VM instance with Debian 10 operating system and a custom hostname. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + hostname: the hostname you want to use for the new instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-11" + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + instance = create_instance(project_id, zone, instance_name, disks, custom_hostname=hostname) + return instance +# diff --git a/compute/client_library/ingredients/instances/custom_hostname/get.py b/compute/client_library/ingredients/instances/custom_hostname/get.py new file mode 100644 index 00000000000..b362fce26e1 --- /dev/null +++ b/compute/client_library/ingredients/instances/custom_hostname/get.py @@ -0,0 +1,40 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def get_hostname(project_id: str, zone: str, instance_name: str) -> str: + """ + Retrieve the hostname of given instance. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: "us-west3-b" + instance_name: name of the virtual machine to check. + + Returns: + The hostname of an instance. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + return instance.hostname +# diff --git a/compute/client_library/ingredients/instances/custom_machine_types/create_extra_mem_no_helper.py b/compute/client_library/ingredients/instances/custom_machine_types/create_extra_mem_no_helper.py new file mode 100644 index 00000000000..536455f669d --- /dev/null +++ b/compute/client_library/ingredients/instances/custom_machine_types/create_extra_mem_no_helper.py @@ -0,0 +1,71 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import List + +from google.cloud import compute_v1 + + +# +def create_custom_instances_extra_mem( + project_id: str, zone: str, instance_name: str, core_count: int, memory: int +) -> List[compute_v1.Instance]: + """ + Create 3 new VM instances with extra memory without using a CustomMachineType helper class. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + core_count: number of CPU cores you want to use. + memory: the amount of memory for the VM instance, in megabytes. + + Returns: + List of Instance objects. + """ + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-10" + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + # The core_count and memory values are not validated anywhere and can be rejected by the API. + instances = [ + create_instance( + project_id, + zone, + f"{instance_name}_n1_extra_mem", + disks, + f"zones/{zone}/machineTypes/custom-{core_count}-{memory}-ext", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2_extra_mem", + disks, + f"zones/{zone}/machineTypes/n2-custom-{core_count}-{memory}-ext", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2d_extra_mem", + disks, + f"zones/{zone}/machineTypes/n2d-custom-{core_count}-{memory}-ext", + ), + ] + return instances +# diff --git a/compute/client_library/ingredients/instances/custom_machine_types/create_shared_with_helper.py b/compute/client_library/ingredients/instances/custom_machine_types/create_shared_with_helper.py new file mode 100644 index 00000000000..a29193438ff --- /dev/null +++ b/compute/client_library/ingredients/instances/custom_machine_types/create_shared_with_helper.py @@ -0,0 +1,60 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + + +from google.cloud import compute_v1 + + +# +def create_custom_shared_core_instance( + project_id: str, + zone: str, + instance_name: str, + cpu_series: CustomMachineType.CPUSeries, + memory: int, +) -> compute_v1.Instance: + """ + Create a new VM instance with a custom type using shared CPUs. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + cpu_series: the type of CPU you want to use. Pick one value from the CustomMachineType.CPUSeries enum. + For example: CustomMachineType.CPUSeries.E2_MICRO + memory: the amount of memory for the VM instance, in megabytes. + + Return: + Instance object. + """ + assert cpu_series in ( + CustomMachineType.CPUSeries.E2_MICRO, + CustomMachineType.CPUSeries.E2_SMALL, + CustomMachineType.CPUSeries.E2_MEDIUM, + ) + custom_type = CustomMachineType(zone, cpu_series, memory) + + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-10" + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + + return create_instance(project_id, zone, instance_name, disks, str(custom_type)) +# diff --git a/compute/client_library/ingredients/instances/custom_machine_types/create_with_helper.py b/compute/client_library/ingredients/instances/custom_machine_types/create_with_helper.py new file mode 100644 index 00000000000..2731f40da89 --- /dev/null +++ b/compute/client_library/ingredients/instances/custom_machine_types/create_with_helper.py @@ -0,0 +1,61 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def create_custom_instance( + project_id: str, + zone: str, + instance_name: str, + cpu_series: CustomMachineType.CPUSeries, + core_count: int, + memory: int, +) -> compute_v1.Instance: + """ + Create a new VM instance with a custom machine type. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + cpu_series: the type of CPU you want to use. Select one value from the CustomMachineType.CPUSeries enum. + For example: CustomMachineType.CPUSeries.N2 + core_count: number of CPU cores you want to use. + memory: the amount of memory for the VM instance, in megabytes. + + Return: + Instance object. + """ + assert cpu_series in ( + CustomMachineType.CPUSeries.E2, + CustomMachineType.CPUSeries.N1, + CustomMachineType.CPUSeries.N2, + CustomMachineType.CPUSeries.N2D, + ) + custom_type = CustomMachineType(zone, cpu_series, memory, core_count) + + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-10" + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + + return create_instance(project_id, zone, instance_name, disks, str(custom_type)) +# diff --git a/compute/client_library/ingredients/instances/custom_machine_types/create_without_helper.py b/compute/client_library/ingredients/instances/custom_machine_types/create_without_helper.py new file mode 100644 index 00000000000..a17a979bb8f --- /dev/null +++ b/compute/client_library/ingredients/instances/custom_machine_types/create_without_helper.py @@ -0,0 +1,60 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + + +from typing import List + +from google.cloud import compute_v1 + + +# +def create_custom_instances_no_helper( + project_id: str, zone: str, instance_name: str, core_count: int, memory: int +) -> List[compute_v1.Instance]: + """ + Create 7 new VM instances without using a CustomMachineType helper function. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + core_count: number of CPU cores you want to use. + memory: the amount of memory for the VM instance, in megabytes. + + Returns: + List of Instance objects. + """ + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-10" + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + params = [ + (f"{instance_name}_n1", f"zones/{zone}/machineTypes/custom-{core_count}-{memory}"), + (f"{instance_name}_n2", f"zones/{zone}/machineTypes/n2-custom-{core_count}-{memory}"), + (f"{instance_name}_n2d", f"zones/{zone}/machineTypes/n2d-custom-{core_count}-{memory}"), + (f"{instance_name}_e2", f"zones/{zone}/machineTypes/e2-custom-{core_count}-{memory}"), + (f"{instance_name}_e2_micro", f"zones/{zone}/machineTypes/e2-custom-micro-{memory}"), + (f"{instance_name}_e2_small", f"zones/{zone}/machineTypes/e2-custom-small-{memory}"), + (f"{instance_name}_e2_medium", f"zones/{zone}/machineTypes/e2-custom-medium-{memory}"), + ] + # The core_count and memory values are not validated anywhere and can be rejected by the API. + instances = [create_instance(project_id, zone, name, disks, type) for name, type in params] + return instances +# diff --git a/compute/client_library/ingredients/instances/custom_machine_types/helper_class.py b/compute/client_library/ingredients/instances/custom_machine_types/helper_class.py new file mode 100644 index 00000000000..616961943b8 --- /dev/null +++ b/compute/client_library/ingredients/instances/custom_machine_types/helper_class.py @@ -0,0 +1,211 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from collections import namedtuple +from enum import Enum, unique + + +# +def gb_to_mb(value: int) -> int: + return value << 10 + + +class CustomMachineType: + """ + Allows to create custom machine types to be used with the VM instances. + """ + + @unique + class CPUSeries(Enum): + N1 = "custom" + N2 = "n2-custom" + N2D = "n2d-custom" + E2 = "e2-custom" + E2_MICRO = "e2-custom-micro" + E2_SMALL = "e2-custom-small" + E2_MEDIUM = "e2-custom-medium" + + TypeLimits = namedtuple( + "TypeLimits", + [ + "allowed_cores", + "min_mem_per_core", + "max_mem_per_core", + "allow_extra_memory", + "extra_memory_limit", + ], + ) + + # The limits for various CPU types are described on: + # https://cloud.google.com/compute/docs/general-purpose-machines + LIMITS = { + CPUSeries.E2: TypeLimits(frozenset(range(2, 33, 2)), 512, 8192, False, 0), + CPUSeries.E2_MICRO: TypeLimits(frozenset(), 1024, 2048, False, 0), + CPUSeries.E2_SMALL: TypeLimits(frozenset(), 2048, 4096, False, 0), + CPUSeries.E2_MEDIUM: TypeLimits(frozenset(), 4096, 8192, False, 0), + CPUSeries.N2: TypeLimits( + frozenset(range(2, 33, 2)).union(set(range(36, 129, 4))), + 512, + 8192, + True, + gb_to_mb(624), + ), + CPUSeries.N2D: TypeLimits( + frozenset({2, 4, 8, 16, 32, 48, 64, 80, 96}), 512, 8192, True, gb_to_mb(768) + ), + CPUSeries.N1: TypeLimits( + frozenset({1}.union(range(2, 97, 2))), 922, 6656, True, gb_to_mb(624) + ), + } + + def __init__( + self, zone: str, cpu_series: CPUSeries, memory_mb: int, core_count: int = 0 + ): + self.zone = zone + self.cpu_series = cpu_series + self.limits = self.LIMITS[self.cpu_series] + # Shared machine types (e2-small, e2-medium and e2-micro) always have + # 2 vCPUs: https://cloud.google.com/compute/docs/general-purpose-machines#e2_limitations + self.core_count = 2 if self.is_shared() else core_count + self.memory_mb = memory_mb + self._checked = False + self._check_parameters() + self.extra_memory_used = self._check_extra_memory() + + def is_shared(self): + return self.cpu_series in ( + CustomMachineType.CPUSeries.E2_SMALL, + CustomMachineType.CPUSeries.E2_MICRO, + CustomMachineType.CPUSeries.E2_MEDIUM, + ) + + def _check_extra_memory(self) -> bool: + if self._checked: + return self.memory_mb > self.core_count * self.limits.max_mem_per_core + else: + raise RuntimeError("You need to call _check_parameters() before calling _check_extra_memory()") + + def _check_parameters(self): + """ + Check whether the requested parameters are allowed. Find more information about limitations of custom machine + types at: https://cloud.google.com/compute/docs/general-purpose-machines#custom_machine_types + """ + # Check the number of cores + if ( + self.limits.allowed_cores + and self.core_count not in self.limits.allowed_cores + ): + raise RuntimeError( + f"Invalid number of cores requested. Allowed number of cores for {self.cpu_series.name} is: {sorted(self.limits.allowed_cores)}" + ) + + # Memory must be a multiple of 256 MB + if self.memory_mb % 256 != 0: + raise RuntimeError("Requested memory must be a multiple of 256 MB.") + + # Check if the requested memory isn't too little + if self.memory_mb < self.core_count * self.limits.min_mem_per_core: + raise RuntimeError( + f"Requested memory is too low. Minimal memory for {self.cpu_series.name} is {self.limits.min_mem_per_core} MB per core." + ) + + # Check if the requested memory isn't too much + if self.memory_mb > self.core_count * self.limits.max_mem_per_core: + if self.limits.allow_extra_memory: + if self.memory_mb > self.limits.extra_memory_limit: + raise RuntimeError( + f"Requested memory is too large.. Maximum memory allowed for {self.cpu_series.name} is {self.limits.extra_memory_limit} MB." + ) + else: + raise RuntimeError( + f"Requested memory is too large.. Maximum memory allowed for {self.cpu_series.name} is {self.limits.max_mem_per_core} MB per core." + ) + + self._checked = True + + def __str__(self) -> str: + """ + Return the custom machine type in form of a string acceptable by Compute Engine API. + """ + if self.cpu_series in { + self.CPUSeries.E2_SMALL, + self.CPUSeries.E2_MICRO, + self.CPUSeries.E2_MEDIUM, + }: + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.memory_mb}" + + if self.extra_memory_used: + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.core_count}-{self.memory_mb}-ext" + + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.core_count}-{self.memory_mb}" + + def short_type_str(self) -> str: + """ + Return machine type in a format without the zone. For example, n2-custom-0-10240. + This format is used to create instance templates. + """ + return str(self).rsplit("/", maxsplit=1)[1] + + @classmethod + def from_str(cls, machine_type: str): + """ + Construct a new object from a string. The string needs to be a valid custom machine type like: + - https://www.googleapis.com/compute/v1/projects/diregapic-mestiv/zones/us-central1-b/machineTypes/e2-custom-4-8192 + - zones/us-central1-b/machineTypes/e2-custom-4-8192 + - e2-custom-4-8192 (in this case, the zone parameter will not be set) + """ + zone = None + if machine_type.startswith("http"): + machine_type = machine_type[machine_type.find("zones/") :] + + if machine_type.startswith("zones/"): + _, zone, _, machine_type = machine_type.split("/") + + extra_mem = machine_type.endswith("-ext") + + if machine_type.startswith("custom"): + cpu = cls.CPUSeries.N1 + _, cores, memory = machine_type.rsplit("-", maxsplit=2) + else: + if extra_mem: + cpu_series, _, cores, memory, _ = machine_type.split("-") + else: + cpu_series, _, cores, memory = machine_type.split("-") + if cpu_series == "n2": + cpu = cls.CPUSeries.N2 + elif cpu_series == "n2d": + cpu = cls.CPUSeries.N2D + elif cpu_series == "e2": + cpu = cls.CPUSeries.E2 + if cores == "micro": + cpu = cls.CPUSeries.E2_MICRO + cores = 2 + elif cores == "small": + cpu = cls.CPUSeries.E2_SMALL + cores = 2 + elif cores == "medium": + cpu = cls.CPUSeries.E2_MEDIUM + cores = 2 + else: + raise RuntimeError("Unknown CPU series.") + + cores = int(cores) + memory = int(memory) + + return cls(zone, cpu, memory, cores) +# diff --git a/compute/client_library/ingredients/instances/custom_machine_types/update_memory.py b/compute/client_library/ingredients/instances/custom_machine_types/update_memory.py new file mode 100644 index 00000000000..fd1afac8912 --- /dev/null +++ b/compute/client_library/ingredients/instances/custom_machine_types/update_memory.py @@ -0,0 +1,88 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import time + +from google.cloud import compute_v1 + + +# +def add_extended_memory_to_instance( + project_id: str, zone: str, instance_name: str, new_memory: int +): + """ + Modify an existing VM to use extended memory. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + new_memory: the amount of memory for the VM instance, in megabytes. + + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + + if not ("n1-" in instance.machine_type or "n2-" in instance.machine_type or "n2d-" in instance.machine_type): + raise RuntimeError("Extra memory is available only for N1, N2 and N2D CPUs.") + + # Make sure that the machine is turned off + if instance.status not in ( + instance.Status.TERMINATED.name, + instance.Status.STOPPED.name, + ): + operation = instance_client.stop( + project=project_id, zone=zone, instance=instance_name + ) + wait_for_extended_operation(operation, "instance stopping") + start = time.time() + while instance.status not in ( + instance.Status.TERMINATED.name, + instance.Status.STOPPED.name, + ): + # Waiting for the instance to be turned off. + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + time.sleep(2) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() + + # Modify the machine definition, remember that extended memory is available only for N1, N2 and N2D CPUs + start, end = instance.machine_type.rsplit("-", maxsplit=1) + instance.machine_type = start + f"-{new_memory}-ext" + # TODO: If you prefer to use the CustomMachineType helper class, uncomment this code and comment the 2 lines above + # Using CustomMachineType helper + # cmt = CustomMachineType.from_str(instance.machine_type) + # cmt.memory_mb = new_memory + # cmt.extra_memory_used = True + # instance.machine_type = str(cmt) + operation = instance_client.update( + project=project_id, + zone=zone, + instance=instance_name, + instance_resource=instance, + ) + wait_for_extended_operation(operation, "instance update") + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) +# diff --git a/compute/client_library/ingredients/instances/delete.py b/compute/client_library/ingredients/instances/delete.py new file mode 100644 index 00000000000..bd16d439ae9 --- /dev/null +++ b/compute/client_library/ingredients/instances/delete.py @@ -0,0 +1,44 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import sys +import time + +from google.cloud import compute_v1 + + +# +def delete_instance(project_id: str, zone: str, machine_name: str) -> None: + """ + Send an instance deletion request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: “us-west3-b” + machine_name: name of the machine you want to delete. + """ + instance_client = compute_v1.InstancesClient() + + print(f"Deleting {machine_name} from {zone}...") + operation = instance_client.delete( + project=project_id, zone=zone, instance=machine_name + ) + wait_for_extended_operation(operation, "instance deletion") + print(f"Instance {machine_name} deleted.") + return +# diff --git a/compute/client_library/ingredients/instances/delete_protection/__init__.py b/compute/client_library/ingredients/instances/delete_protection/__init__.py new file mode 100644 index 00000000000..8fb7cb024dd --- /dev/null +++ b/compute/client_library/ingredients/instances/delete_protection/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + diff --git a/compute/client_library/ingredients/instances/delete_protection/create.py b/compute/client_library/ingredients/instances/delete_protection/create.py new file mode 100644 index 00000000000..eb431ec7e84 --- /dev/null +++ b/compute/client_library/ingredients/instances/delete_protection/create.py @@ -0,0 +1,44 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def create_protected_instance(project_id: str, zone: str, instance_name: str) -> compute_v1.Instance: + """ + Create a new VM instance with Debian 10 operating system and delete protection + turned on. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-11" + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + instance = create_instance(project_id, zone, instance_name, disks, delete_protection=True) + return instance +# \ No newline at end of file diff --git a/compute/client_library/ingredients/instances/delete_protection/get.py b/compute/client_library/ingredients/instances/delete_protection/get.py new file mode 100644 index 00000000000..f57b1624e05 --- /dev/null +++ b/compute/client_library/ingredients/instances/delete_protection/get.py @@ -0,0 +1,38 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def get_delete_protection(project_id: str, zone: str, instance_name: str) -> bool: + """ + Returns the state of delete protection flag of given instance. + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: “us-west3-b” + instance_name: name of the virtual machine to check. + Returns: + The boolean value of the delete protection setting. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + return instance.deletion_protection +# diff --git a/compute/client_library/ingredients/instances/delete_protection/set.py b/compute/client_library/ingredients/instances/delete_protection/set.py new file mode 100644 index 00000000000..4d3a73a5be7 --- /dev/null +++ b/compute/client_library/ingredients/instances/delete_protection/set.py @@ -0,0 +1,46 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def set_delete_protection( + project_id: str, zone: str, instance_name: str, delete_protection: bool +) -> None: + """ + Updates the delete protection setting of given instance. + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: “us-west3-b” + instance_name: name of the instance to update. + delete_protection: boolean value indicating if the virtual machine should be + protected against deletion or not. + """ + instance_client = compute_v1.InstancesClient() + + request = compute_v1.SetDeletionProtectionInstanceRequest() + request.project = project_id + request.zone = zone + request.resource = instance_name + request.deletion_protection = delete_protection + + operation = instance_client.set_deletion_protection(request) + wait_for_extended_operation(operation, "changing delete protection setting") + return +# diff --git a/compute/client_library/ingredients/instances/get.py b/compute/client_library/ingredients/instances/get.py new file mode 100644 index 00000000000..702696ad789 --- /dev/null +++ b/compute/client_library/ingredients/instances/get.py @@ -0,0 +1,39 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def get_instance(project_id: str, zone: str, instance_name: str) -> compute_v1.Instance: + """ + Get information about a VM instance in the given zone in the specified project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: “us-west3-b” + instance_name: name of the VM instance you want to query. + Returns: + An Instance object. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get(project=project_id, zone=zone, instance=instance_name) + + return instance +# + diff --git a/compute/client_library/ingredients/instances/get_serial_port.py b/compute/client_library/ingredients/instances/get_serial_port.py new file mode 100644 index 00000000000..a673e1d27e1 --- /dev/null +++ b/compute/client_library/ingredients/instances/get_serial_port.py @@ -0,0 +1,38 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def get_instance_serial_port_output(project_id: str, zone: str, instance_name: str) -> compute_v1.SerialPortOutput: + """ + Returns the last 1 MB of serial port output from the specified instance. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: “us-west3-b” + instance_name: name of the VM instance you want to query. + Returns: + Content of the serial port output of an instance inside a compute_v1.SerialPortOutput object. + More about this type: https://cloud.google.com/python/docs/reference/compute/latest/google.cloud.compute_v1.types.SerialPortOutput + + """ + instance_client = compute_v1.InstancesClient() + return instance_client.get_serial_port_output(project=project_id, zone=zone, instance=instance_name) +# diff --git a/compute/client_library/ingredients/instances/list.py b/compute/client_library/ingredients/instances/list.py new file mode 100644 index 00000000000..089f7fdabb8 --- /dev/null +++ b/compute/client_library/ingredients/instances/list.py @@ -0,0 +1,44 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import Iterable + +from google.cloud import compute_v1 + + +# +def list_instances(project_id: str, zone: str) -> Iterable[compute_v1.Instance]: + """ + List all instances in the given zone in the specified project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: “us-west3-b” + Returns: + An iterable collection of Instance objects. + """ + instance_client = compute_v1.InstancesClient() + instance_list = instance_client.list(project=project_id, zone=zone) + + print(f"Instances found in zone {zone}:") + for instance in instance_list: + print(f" - {instance.name} ({instance.machine_type})") + + return instance_list +# + diff --git a/compute/client_library/ingredients/instances/list_all.py b/compute/client_library/ingredients/instances/list_all.py new file mode 100644 index 00000000000..60498c4df67 --- /dev/null +++ b/compute/client_library/ingredients/instances/list_all.py @@ -0,0 +1,58 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from collections import defaultdict +from typing import Dict, Iterable + +from google.cloud import compute_v1 + + +# +def list_all_instances( + project_id: str, +) -> Dict[str, Iterable[compute_v1.Instance]]: + """ + Returns a dictionary of all instances present in a project, grouped by their zone. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + Returns: + A dictionary with zone names as keys (in form of "zones/{zone_name}") and + iterable collections of Instance objects as values. + """ + instance_client = compute_v1.InstancesClient() + request = compute_v1.AggregatedListInstancesRequest() + request.project = project_id + # Use the `max_results` parameter to limit the number of results that the API returns per response page. + request.max_results = 50 + + agg_list = instance_client.aggregated_list(request=request) + + all_instances = defaultdict(list) + print("Instances found:") + # Despite using the `max_results` parameter, you don't need to handle the pagination + # yourself. The returned `AggregatedListPager` object handles pagination + # automatically, returning separated pages as you iterate over the results. + for zone, response in agg_list: + if response.instances: + all_instances[zone].extend(response.instances) + print(f" {zone}:") + for instance in response.instances: + print(f" - {instance.name} ({instance.machine_type})") + return all_instances +# diff --git a/compute/client_library/ingredients/instances/preemptible/__init__.py b/compute/client_library/ingredients/instances/preemptible/__init__.py new file mode 100644 index 00000000000..81d8b9be3da --- /dev/null +++ b/compute/client_library/ingredients/instances/preemptible/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa diff --git a/compute/client_library/ingredients/instances/preemptible/create.py b/compute/client_library/ingredients/instances/preemptible/create.py new file mode 100644 index 00000000000..145fbc3679f --- /dev/null +++ b/compute/client_library/ingredients/instances/preemptible/create.py @@ -0,0 +1,43 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def create_preemptible_instance(project_id: str, zone: str, instance_name: str) -> compute_v1.Instance: + """ + Create a new preemptible VM instance with Debian 10 operating system. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-11" + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + instance = create_instance(project_id, zone, instance_name, disks, preemptible=True) + return instance +# diff --git a/compute/client_library/ingredients/instances/preemptible/get.py b/compute/client_library/ingredients/instances/preemptible/get.py new file mode 100644 index 00000000000..7ff9fc860e6 --- /dev/null +++ b/compute/client_library/ingredients/instances/preemptible/get.py @@ -0,0 +1,38 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def is_preemptible(project_id: str, zone: str, instance_name: str) -> bool: + """ + Check if a given instance is preemptible or not. + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: "us-west3-b" + instance_name: name of the virtual machine to check. + Returns: + The preemptible status of the instance. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + return instance.scheduling.preemptible +# diff --git a/compute/client_library/ingredients/instances/preemptible/preemption_history.py b/compute/client_library/ingredients/instances/preemptible/preemption_history.py new file mode 100644 index 00000000000..53b6a3da767 --- /dev/null +++ b/compute/client_library/ingredients/instances/preemptible/preemption_history.py @@ -0,0 +1,56 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import datetime +from typing import List, Tuple + + +# +def preemption_history( + project_id: str, zone: str, instance_name: str = None +) -> List[Tuple[str, datetime.datetime]]: + """ + Get a list of preemption operations from given zone in a project. Optionally limit + the results to instance name. + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: "us-west3-b" + instance_name: name of the virtual machine to look for. + Returns: + List of preemption operations in given zone. + """ + if instance_name: + filter = ( + f'operationType="compute.instances.preempted" ' + f"AND targetLink:instances/{instance_name}" + ) + else: + filter = 'operationType="compute.instances.preempted"' + + history = [] + + for operation in list_zone_operations(project_id, zone, filter): + this_instance_name = operation.target_link.rsplit("/", maxsplit=1)[1] + if instance_name and this_instance_name == instance_name: + # The filter used is not 100% accurate, it's `contains` not `equals` + # So we need to check the name to make sure it's the one we want. + moment = datetime.datetime.fromisoformat(operation.insert_time) + history.append((instance_name, moment)) + + return history +# diff --git a/compute/client_library/ingredients/instances/reset.py b/compute/client_library/ingredients/instances/reset.py new file mode 100644 index 00000000000..f784e4b91dc --- /dev/null +++ b/compute/client_library/ingredients/instances/reset.py @@ -0,0 +1,42 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import time + +from google.cloud import compute_v1 + + +# +def reset_instance(project_id: str, zone: str, instance_name: str) -> None: + """ + Resets a stopped Google Compute Engine instance (with unencrypted disks). + Args: + project_id: project ID or project number of the Cloud project your instance belongs to. + zone: name of the zone your instance belongs to. + instance_name: name of the instance your want to reset. + """ + instance_client = compute_v1.InstancesClient() + + operation = instance_client.reset( + project=project_id, zone=zone, instance=instance_name + ) + + wait_for_extended_operation(operation, "instance reset") + + return +# diff --git a/compute/client_library/ingredients/instances/resume.py b/compute/client_library/ingredients/instances/resume.py new file mode 100644 index 00000000000..f29ffab6aee --- /dev/null +++ b/compute/client_library/ingredients/instances/resume.py @@ -0,0 +1,47 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import time + +from google.cloud import compute_v1 + + +# +def resume_instance(project_id: str, zone: str, instance_name: str) -> None: + """ + Resume a suspended Google Compute Engine instance (with unencrypted disks). + Args: + project_id: project ID or project number of the Cloud project your instance belongs to. + zone: name of the zone your instance belongs to. + instance_name: name of the instance you want to resume. + """ + instance_client = compute_v1.InstancesClient() + + instance = instance_client.get(project=project_id, zone=zone, instance=instance_name) + if instance.status != compute_v1.Instance.Status.SUSPENDED.name: + raise RuntimeError(f"Only suspended instances can be resumed. " + f"Instance {instance_name} is in {instance.status} state.") + + operation = instance_client.resume( + project=project_id, zone=zone, instance=instance_name + ) + + wait_for_extended_operation(operation, "instance resumption") + return +# + diff --git a/compute/client_library/ingredients/instances/spot/create.py b/compute/client_library/ingredients/instances/spot/create.py new file mode 100644 index 00000000000..ba87875cf8e --- /dev/null +++ b/compute/client_library/ingredients/instances/spot/create.py @@ -0,0 +1,43 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa + +from google.cloud import compute_v1 + + +# +def create_spot_instance(project_id: str, zone: str, instance_name: str) -> compute_v1.Instance: + """ + Create a new Spot VM instance with Debian 10 operating system. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-11" + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + instance = create_instance(project_id, zone, instance_name, disks, spot=True) + return instance +# diff --git a/compute/client_library/ingredients/instances/spot/get.py b/compute/client_library/ingredients/instances/spot/get.py new file mode 100644 index 00000000000..3366cf36d59 --- /dev/null +++ b/compute/client_library/ingredients/instances/spot/get.py @@ -0,0 +1,39 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def is_spot_vm(project_id: str, zone: str, instance_name: str) -> bool: + """ + Check if a given instance is Spot VM or not. + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: "us-west3-b" + instance_name: name of the virtual machine to check. + Returns: + The Spot VM status of the instance. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + return instance.scheduling.provisioning_model == compute_v1.Scheduling.ProvisioningModel.SPOT.name +# + diff --git a/compute/client_library/ingredients/instances/start.py b/compute/client_library/ingredients/instances/start.py new file mode 100644 index 00000000000..eeba32eb77c --- /dev/null +++ b/compute/client_library/ingredients/instances/start.py @@ -0,0 +1,41 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import time + +from google.cloud import compute_v1 + + +# +def start_instance(project_id: str, zone: str, instance_name: str) -> None: + """ + Starts a stopped Google Compute Engine instance (with unencrypted disks). + Args: + project_id: project ID or project number of the Cloud project your instance belongs to. + zone: name of the zone your instance belongs to. + instance_name: name of the instance your want to start. + """ + instance_client = compute_v1.InstancesClient() + + operation = instance_client.start( + project=project_id, zone=zone, instance=instance_name + ) + + wait_for_extended_operation(operation, "instance start") + return +# diff --git a/compute/client_library/ingredients/instances/start_encrypted.py b/compute/client_library/ingredients/instances/start_encrypted.py new file mode 100644 index 00000000000..201d2233d36 --- /dev/null +++ b/compute/client_library/ingredients/instances/start_encrypted.py @@ -0,0 +1,63 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import time + +from google.cloud import compute_v1 + + +# +def start_instance_with_encryption_key( + project_id: str, zone: str, instance_name: str, key: bytes +): + """ + Starts a stopped Google Compute Engine instance (with encrypted disks). + Args: + project_id: project ID or project number of the Cloud project your instance belongs to. + zone: name of the zone your instance belongs to. + instance_name: name of the instance your want to start. + key: bytes object representing a raw base64 encoded key to your machines boot disk. + For more information about disk encryption see: + https://cloud.google.com/compute/docs/disks/customer-supplied-encryption#specifications + """ + instance_client = compute_v1.InstancesClient() + + instance_data = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + + # Prepare the information about disk encryption + disk_data = compute_v1.CustomerEncryptionKeyProtectedDisk() + disk_data.source = instance_data.disks[0].source + disk_data.disk_encryption_key = compute_v1.CustomerEncryptionKey() + # Use raw_key to send over the key to unlock the disk + # To use a key stored in KMS, you need to provide `kms_key_name` and `kms_key_service_account` + disk_data.disk_encryption_key.raw_key = key + enc_data = compute_v1.InstancesStartWithEncryptionKeyRequest() + enc_data.disks = [disk_data] + + operation = instance_client.start_with_encryption_key( + project=project_id, + zone=zone, + instance=instance_name, + instances_start_with_encryption_key_request_resource=enc_data, + ) + + wait_for_extended_operation(operation, "instance start (with encrypted disk)") + return +# diff --git a/compute/client_library/ingredients/instances/stop.py b/compute/client_library/ingredients/instances/stop.py new file mode 100644 index 00000000000..ea1b532f89e --- /dev/null +++ b/compute/client_library/ingredients/instances/stop.py @@ -0,0 +1,40 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import time + +from google.cloud import compute_v1 + + +# +def stop_instance(project_id: str, zone: str, instance_name: str) -> None: + """ + Stops a running Google Compute Engine instance. + Args: + project_id: project ID or project number of the Cloud project your instance belongs to. + zone: name of the zone your instance belongs to. + instance_name: name of the instance your want to stop. + """ + instance_client = compute_v1.InstancesClient() + + operation = instance_client.stop( + project=project_id, zone=zone, instance=instance_name + ) + wait_for_extended_operation(operation, "instance stopping") + return +# diff --git a/compute/client_library/ingredients/instances/suspend.py b/compute/client_library/ingredients/instances/suspend.py new file mode 100644 index 00000000000..64a15da740e --- /dev/null +++ b/compute/client_library/ingredients/instances/suspend.py @@ -0,0 +1,42 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import time + +from google.cloud import compute_v1 + + +# +def suspend_instance(project_id: str, zone: str, instance_name: str) -> None: + """ + Suspend a running Google Compute Engine instance. + Args: + project_id: project ID or project number of the Cloud project your instance belongs to. + zone: name of the zone your instance belongs to. + instance_name: name of the instance you want to suspend. + """ + instance_client = compute_v1.InstancesClient() + + operation = instance_client.suspend( + project=project_id, zone=zone, instance=instance_name + ) + + wait_for_extended_operation(operation, "suspend instance") + return +# + diff --git a/compute/client_library/ingredients/operations/__init__.py b/compute/client_library/ingredients/operations/__init__.py new file mode 100644 index 00000000000..81d8b9be3da --- /dev/null +++ b/compute/client_library/ingredients/operations/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa diff --git a/compute/client_library/ingredients/operations/handle_extended_operation.py b/compute/client_library/ingredients/operations/handle_extended_operation.py new file mode 100644 index 00000000000..1a0c74213eb --- /dev/null +++ b/compute/client_library/ingredients/operations/handle_extended_operation.py @@ -0,0 +1,69 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +import sys +from typing import Any + +from google.api_core.extended_operation import ExtendedOperation + + +# +def wait_for_extended_operation( + operation: ExtendedOperation, + verbose_name: str = "operation", + timeout: int = 300) -> Any: + """ + This method will wait 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 +# diff --git a/compute/client_library/ingredients/operations/list_zone_operations.py b/compute/client_library/ingredients/operations/list_zone_operations.py new file mode 100644 index 00000000000..472e893d5a2 --- /dev/null +++ b/compute/client_library/ingredients/operations/list_zone_operations.py @@ -0,0 +1,46 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + +from google.cloud.compute_v1.services.zone_operations import pagers + + +# +def list_zone_operations( + project_id: str, zone: str, filter: str = "" +) -> pagers.ListPager: + """ + List all recent operations the happened in given zone in a project. Optionally filter those + operations by providing a filter. More about using the filter can be found here: + https://cloud.google.com/compute/docs/reference/rest/v1/zoneOperations/list + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: "us-west3-b" + filter: filter string to be used for this listing operation. + Returns: + List of preemption operations in given zone. + """ + operation_client = compute_v1.ZoneOperationsClient() + request = compute_v1.ListZoneOperationsRequest() + request.project = project_id + request.zone = zone + request.filter = filter + + return operation_client.list(request) +# diff --git a/compute/client_library/ingredients/operations/wait_for_operation.py b/compute/client_library/ingredients/operations/wait_for_operation.py new file mode 100644 index 00000000000..53913076e69 --- /dev/null +++ b/compute/client_library/ingredients/operations/wait_for_operation.py @@ -0,0 +1,50 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def wait_for_operation( + operation: compute_v1.Operation, project_id: str +) -> compute_v1.Operation: + """ + This method waits for an operation to be completed. Calling this function + will block until the operation is finished. + + Args: + operation: The Operation object representing the operation you want to + wait on. + project_id: project ID or project number of the Cloud project you want to use. + + Returns: + Finished Operation object. + """ + kwargs = {"project": project_id, "operation": operation.name} + if operation.zone: + client = compute_v1.ZoneOperationsClient() + # Operation.zone is a full URL address of a zone, so we need to extract just the name + kwargs["zone"] = operation.zone.rsplit("/", maxsplit=1)[1] + elif operation.region: + client = compute_v1.RegionOperationsClient() + # Operation.region is a full URL address of a region, so we need to extract just the name + kwargs["region"] = operation.region.rsplit("/", maxsplit=1)[1] + else: + client = compute_v1.GlobalOperationsClient() + return client.wait(**kwargs) +# diff --git a/compute/client_library/ingredients/routes/create.py b/compute/client_library/ingredients/routes/create.py new file mode 100644 index 00000000000..a309150c38c --- /dev/null +++ b/compute/client_library/ingredients/routes/create.py @@ -0,0 +1,84 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import Optional + +from google.cloud import compute_v1 + + +# +def create_route(project_id: str, network: str, route_name: str, destination_range: str, *, + next_hop_gateway: Optional[str] = None, + next_hop_ip: Optional[str] = None, next_hop_instance: Optional[str] = None, + next_hop_vpn_tunnel: Optional[str] = None, next_hop_ilb: Optional[str] = None) -> compute_v1.Route: + """ + Create a new route in selected network by providing a destination and next hop name. + + Note: The set of {next_hop_gateway, next_hop_ip, next_hop_instance, next_hop_vpn_tunnel, + next_hop_ilb} is exclusive, you and only specify one of those parameters. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + network: name of the network the route will be created in. Available name formats: + * https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network} + * projects/{project_id}/global/networks/{network} + * global/networks/{network} + route_name: name of the new route. + destination_range: range of destination IPs this route should be applied to. E.g. 10.0.0.0/16. + next_hop_gateway: name of the gateway the traffic should be directed to. + next_hop_ip: IP address the traffic should be directed to. + next_hop_instance: name of the instance the traffic should be directed to. Name format: + "projects/{project}/zones/{zone}/instances/{instance_name}" + next_hop_vpn_tunnel: name of the VPN tunnel the traffic should be directed to. Name format: + "projects/{project}/regions/{region}/vpnTunnels/{vpn_tunnel_name}" + next_hop_ilb: name of a forwarding rule of the Internal Load Balancer the traffic + should be directed to. Name format: + "projects/{project}/regions/{region}/forwardingRules/{forwarding_rule_region}" + + Returns: + A new compute_v1.Route object. + """ + excl_args = {next_hop_instance, next_hop_ilb, next_hop_vpn_tunnel, next_hop_gateway, next_hop_ip} + args_set = sum(1 if arg is not None else 0 for arg in excl_args) + + if args_set != 1: + raise RuntimeError("You must specify exactly one next_hop_* parameter.") + + route = compute_v1.Route() + route.name = route_name + route.network = network + route.dest_range = destination_range + + if next_hop_gateway: + route.next_hop_gateway = next_hop_gateway + elif next_hop_ip: + route.next_hop_ip = next_hop_ip + elif next_hop_instance: + route.next_hop_instance = next_hop_instance + elif next_hop_vpn_tunnel: + route.next_hop_vpn_tunnel = next_hop_vpn_tunnel + elif next_hop_ilb: + route.next_hop_ilb = next_hop_ilb + + route_client = compute_v1.RoutesClient() + operation = route_client.insert(project=project_id, route_resource=route) + + wait_for_extended_operation(operation, "route creation") + + return route_client.get(project=project_id, route=route_name) +# diff --git a/compute/client_library/ingredients/routes/delete.py b/compute/client_library/ingredients/routes/delete.py new file mode 100644 index 00000000000..52328a55906 --- /dev/null +++ b/compute/client_library/ingredients/routes/delete.py @@ -0,0 +1,40 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import NoReturn + +from google.cloud import compute_v1 + + +# +def delete_route(project_id: str, route_name: str) -> NoReturn: + """ + Delete a route in project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + route_name: name of the route to delete. + """ + + route_client = compute_v1.RoutesClient() + operation = route_client.delete(project=project_id, route=route_name) + + wait_for_extended_operation(operation, "route deletion") + + return +# diff --git a/compute/client_library/ingredients/routes/list.py b/compute/client_library/ingredients/routes/list.py new file mode 100644 index 00000000000..494f5da6868 --- /dev/null +++ b/compute/client_library/ingredients/routes/list.py @@ -0,0 +1,38 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import Iterable + +from google.cloud import compute_v1 + + +# +def list_routes(project_id: str, ) -> Iterable[compute_v1.Route]: + """ + Lists routes in project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + + Returns: + An iterable collection of routes found in given project. + """ + + route_client = compute_v1.RoutesClient() + return route_client.list(project=project_id) +# diff --git a/compute/client_library/ingredients/snapshots/create.py b/compute/client_library/ingredients/snapshots/create.py new file mode 100644 index 00000000000..9b0e0cac78b --- /dev/null +++ b/compute/client_library/ingredients/snapshots/create.py @@ -0,0 +1,81 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import Optional + +from google.cloud import compute_v1 + + +# +def create_snapshot(project_id: str, disk_name: str, snapshot_name: str, *, + zone: Optional[str] = None, region: Optional[str] = None, + location: Optional[str] = None, disk_project_id: Optional[str] = None) -> compute_v1.Snapshot: + """ + Create a snapshot of a disk. + + You need to pass `zone` or `region` parameter relevant to the disk you want to + snapshot, but not both. Pass `zone` parameter for zonal disks and `region` for + regional disks. + + Args: + project_id: project ID or project number of the Cloud project you want + to use to store the snapshot. + disk_name: name of the disk you want to snapshot. + snapshot_name: name of the snapshot to be created. + zone: name of the zone in which is the disk you want to snapshot (for zonal disks). + region: name of the region in which is the disk you want to snapshot (for regional disks). + location: The Cloud Storage multi-region or the Cloud Storage region where you + want to store your snapshot. + You can specify only one storage location. Available locations: + https://cloud.google.com/storage/docs/locations#available-locations + disk_project_id: project ID or project number of the Cloud project that + hosts the disk you want to snapshot. If not provided, will look for + the disk in the `project_id` project. + + Returns: + The new snapshot instance. + """ + if zone is None and region is None: + raise RuntimeError("You need to specify `zone` or `region` for this function to work.") + if zone is not None and region is not None: + raise RuntimeError("You can't set both `zone` and `region` parameters.") + + if disk_project_id is None: + disk_project_id = project_id + + if zone is not None: + disk_client = compute_v1.DisksClient() + disk = disk_client.get(project=disk_project_id, zone=zone, disk=disk_name) + else: + regio_disk_client = compute_v1.RegionDisksClient() + disk = regio_disk_client.get(project=disk_project_id, region=region, disk=disk_name) + + snapshot = compute_v1.Snapshot() + snapshot.source_disk = disk.self_link + snapshot.name = snapshot_name + if location: + snapshot.storage_locations = [location] + + snapshot_client = compute_v1.SnapshotsClient() + operation = snapshot_client.insert(project=project_id, snapshot_resource=snapshot) + + wait_for_extended_operation(operation, "snapshot creation") + + return snapshot_client.get(project=project_id, snapshot=snapshot_name) + +# diff --git a/compute/client_library/ingredients/snapshots/delete.py b/compute/client_library/ingredients/snapshots/delete.py new file mode 100644 index 00000000000..e7f5af9bc12 --- /dev/null +++ b/compute/client_library/ingredients/snapshots/delete.py @@ -0,0 +1,40 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import NoReturn + +from google.cloud import compute_v1 + + +# +def delete_snapshot(project_id: str, snapshot_name: str) -> NoReturn: + """ + Delete a snapshot of a disk. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + snapshot_name: name of the snapshot to delete. + """ + + snapshot_client = compute_v1.SnapshotsClient() + operation = snapshot_client.delete(project=project_id, snapshot=snapshot_name) + + wait_for_extended_operation(operation, "snapshot deletion") + + return +# diff --git a/compute/client_library/ingredients/snapshots/get.py b/compute/client_library/ingredients/snapshots/get.py new file mode 100644 index 00000000000..d645880feed --- /dev/null +++ b/compute/client_library/ingredients/snapshots/get.py @@ -0,0 +1,42 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import Iterable + +from google.cloud import compute_v1 + + +# +def get_snapshot(project_id: str, snapshot_name: str) -> compute_v1.Snapshot: + """ + Get information about a Snapshot. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + snapshot_name: the name of the snapshot you want to look up. + + Returns: + A Snapshot object. + """ + + snapshot_client = compute_v1.SnapshotsClient() + + return snapshot_client.get(project=project_id, snapshot=snapshot_name) +# + + diff --git a/compute/client_library/ingredients/snapshots/list.py b/compute/client_library/ingredients/snapshots/list.py new file mode 100644 index 00000000000..a87b2260169 --- /dev/null +++ b/compute/client_library/ingredients/snapshots/list.py @@ -0,0 +1,45 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from typing import Iterable + +from google.cloud import compute_v1 + + +# +def list_snapshots(project_id: str, filter: str = "") -> Iterable[compute_v1.Snapshot]: + """ + List snapshots from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + filter: filter to be applied when listing snapshots. Learn more about filters here: + https://cloud.google.com/python/docs/reference/compute/latest/google.cloud.compute_v1.types.ListSnapshotsRequest + + Returns: + An iterable containing all Snapshots that match the provided filter. + """ + + snapshot_client = compute_v1.SnapshotsClient() + request = compute_v1.ListSnapshotsRequest() + request.project = project_id + request.filter = filter + + return snapshot_client.list(request) +# + diff --git a/compute/client_library/ingredients/usage_report/disable.py b/compute/client_library/ingredients/usage_report/disable.py new file mode 100644 index 00000000000..4fc7ad6c142 --- /dev/null +++ b/compute/client_library/ingredients/usage_report/disable.py @@ -0,0 +1,40 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def disable_usage_export(project_id: str) -> None: + """ + Disable Compute Engine usage export bucket for the Cloud Project. + + Args: + project_id: project ID or project number of the project to update. + """ + projects_client = compute_v1.ProjectsClient() + + # Setting `usage_export_location_resource` to an + # empty object will disable the usage report generation. + operation = projects_client.set_usage_export_bucket( + project=project_id, usage_export_location_resource={} + ) + + wait_for_extended_operation(operation, "disabling GCE usage bucket") +# + diff --git a/compute/client_library/ingredients/usage_report/get_bucket.py b/compute/client_library/ingredients/usage_report/get_bucket.py new file mode 100644 index 00000000000..8b5a3b0b4e4 --- /dev/null +++ b/compute/client_library/ingredients/usage_report/get_bucket.py @@ -0,0 +1,55 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def get_usage_export_bucket(project_id: str) -> compute_v1.UsageExportLocation: + """ + Retrieve Compute Engine usage export bucket for the Cloud project. + Replaces the empty value returned by the API with the default value used + to generate report file names. + + Args: + project_id: project ID or project number of the project to update. + Returns: + UsageExportLocation object describing the current usage export settings + for project project_id. + """ + projects_client = compute_v1.ProjectsClient() + project_data = projects_client.get(project=project_id) + + uel = project_data.usage_export_location + + if not uel.bucket_name: + # The usage reports are disabled. + return uel + + if not uel.report_name_prefix: + # Although the server sent the empty string value, the next usage report + # generated with these settings still has the default prefix value + # "usage_gce". (see https://cloud.google.com/compute/docs/reference/rest/v1/projects/get) + print( + "Report name prefix not set, replacing with default value of " + "`usage_gce`." + ) + uel.report_name_prefix = "usage_gce" + return uel +# + diff --git a/compute/client_library/ingredients/usage_report/set_bucket.py b/compute/client_library/ingredients/usage_report/set_bucket.py new file mode 100644 index 00000000000..f308257df33 --- /dev/null +++ b/compute/client_library/ingredients/usage_report/set_bucket.py @@ -0,0 +1,58 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets +# folder for complete code samples that are ready to be used. +# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check. +# flake8: noqa +from google.cloud import compute_v1 + + +# +def set_usage_export_bucket( + project_id: str, bucket_name: str, report_name_prefix: str = "" +) -> None: + """ + Set Compute Engine usage export bucket for the Cloud project. + This sample presents how to interpret the default value for the + report name prefix parameter. + + Args: + project_id: project ID or project number of the project to update. + bucket_name: Google Cloud Storage bucket used to store Compute Engine + usage reports. An existing Google Cloud Storage bucket is required. + report_name_prefix: Prefix of the usage report name which defaults to an empty string + to showcase default values behaviour. + """ + usage_export_location = compute_v1.UsageExportLocation() + usage_export_location.bucket_name = bucket_name + usage_export_location.report_name_prefix = report_name_prefix + + if not report_name_prefix: + # Sending an empty value for report_name_prefix results in the + # next usage report being generated with the default prefix value + # "usage_gce". (ref: https://cloud.google.com/compute/docs/reference/rest/v1/projects/setUsageExportBucket) + print( + "Setting report_name_prefix to empty value causes the report " + "to have the default prefix of `usage_gce`." + ) + + projects_client = compute_v1.ProjectsClient() + operation = projects_client.set_usage_export_bucket( + project=project_id, usage_export_location_resource=usage_export_location + ) + + wait_for_extended_operation(operation, "setting GCE usage bucket") +# + diff --git a/compute/client_library/noxfile_config.py b/compute/client_library/noxfile_config.py new file mode 100644 index 00000000000..13928a8af82 --- /dev/null +++ b/compute/client_library/noxfile_config.py @@ -0,0 +1,18 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +TEST_CONFIG_OVERRIDE = { + # Tests in test_default_values.py require separate projects to not interfere with each other. + "gcloud_project_env": "BUILD_SPECIFIC_GCLOUD_PROJECT", +} diff --git a/compute/client_library/recipes/__init__.py b/compute/client_library/recipes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/recipes/disks/__init__.py b/compute/client_library/recipes/disks/__init__.py new file mode 100644 index 00000000000..4bbe0ffdb06 --- /dev/null +++ b/compute/client_library/recipes/disks/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/compute/client_library/recipes/disks/autodelete_change.py b/compute/client_library/recipes/disks/autodelete_change.py new file mode 100644 index 00000000000..62465ac913f --- /dev/null +++ b/compute/client_library/recipes/disks/autodelete_change.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/disks/clone_encrypted_disk.py b/compute/client_library/recipes/disks/clone_encrypted_disk.py new file mode 100644 index 00000000000..9ce0ddf795d --- /dev/null +++ b/compute/client_library/recipes/disks/clone_encrypted_disk.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/disks/clone_encrypted_disk_managed_key.py b/compute/client_library/recipes/disks/clone_encrypted_disk_managed_key.py new file mode 100644 index 00000000000..d4b100af644 --- /dev/null +++ b/compute/client_library/recipes/disks/clone_encrypted_disk_managed_key.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/disks/create_empty_disk.py b/compute/client_library/recipes/disks/create_empty_disk.py new file mode 100644 index 00000000000..c044d56f326 --- /dev/null +++ b/compute/client_library/recipes/disks/create_empty_disk.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/disks/create_from_image.py b/compute/client_library/recipes/disks/create_from_image.py new file mode 100644 index 00000000000..a1aa318af70 --- /dev/null +++ b/compute/client_library/recipes/disks/create_from_image.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/disks/create_from_snapshot.py b/compute/client_library/recipes/disks/create_from_snapshot.py new file mode 100644 index 00000000000..efc3e29cc62 --- /dev/null +++ b/compute/client_library/recipes/disks/create_from_snapshot.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/disks/create_from_source.py b/compute/client_library/recipes/disks/create_from_source.py new file mode 100644 index 00000000000..f40f561ec3f --- /dev/null +++ b/compute/client_library/recipes/disks/create_from_source.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/disks/create_kms_encrypted_disk.py b/compute/client_library/recipes/disks/create_kms_encrypted_disk.py new file mode 100644 index 00000000000..06cc0d049d7 --- /dev/null +++ b/compute/client_library/recipes/disks/create_kms_encrypted_disk.py @@ -0,0 +1,24 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# + diff --git a/compute/client_library/recipes/disks/delete.py b/compute/client_library/recipes/disks/delete.py new file mode 100644 index 00000000000..c8bf1d904b3 --- /dev/null +++ b/compute/client_library/recipes/disks/delete.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/disks/list.py b/compute/client_library/recipes/disks/list.py new file mode 100644 index 00000000000..3b0e685faaf --- /dev/null +++ b/compute/client_library/recipes/disks/list.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + diff --git a/compute/client_library/recipes/disks/regional_create_from_source.py b/compute/client_library/recipes/disks/regional_create_from_source.py new file mode 100644 index 00000000000..aceeaa99c9f --- /dev/null +++ b/compute/client_library/recipes/disks/regional_create_from_source.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/disks/regional_delete.py b/compute/client_library/recipes/disks/regional_delete.py new file mode 100644 index 00000000000..986f7dfcda6 --- /dev/null +++ b/compute/client_library/recipes/disks/regional_delete.py @@ -0,0 +1,24 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# + diff --git a/compute/client_library/recipes/firewall/__init__.py b/compute/client_library/recipes/firewall/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/recipes/firewall/create.py b/compute/client_library/recipes/firewall/create.py new file mode 100644 index 00000000000..b4770f78a72 --- /dev/null +++ b/compute/client_library/recipes/firewall/create.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/firewall/delete.py b/compute/client_library/recipes/firewall/delete.py new file mode 100644 index 00000000000..ed25d5d1fe0 --- /dev/null +++ b/compute/client_library/recipes/firewall/delete.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/firewall/list.py b/compute/client_library/recipes/firewall/list.py new file mode 100644 index 00000000000..fbd0149f667 --- /dev/null +++ b/compute/client_library/recipes/firewall/list.py @@ -0,0 +1,21 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# diff --git a/compute/client_library/recipes/firewall/main.py b/compute/client_library/recipes/firewall/main.py new file mode 100644 index 00000000000..c8a4d83b0d3 --- /dev/null +++ b/compute/client_library/recipes/firewall/main.py @@ -0,0 +1,58 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# + + +# + +# + +# + +# + +# + +if __name__ == "__main__": + import google.auth + import google.auth.exceptions + + try: + default_project_id = google.auth.default()[1] + print(f"Using project {default_project_id}.") + except google.auth.exceptions.DefaultCredentialsError: + print( + "Please use `gcloud auth application-default login` " + "or set GOOGLE_APPLICATION_CREDENTIALS to use this script." + ) + else: + import uuid + + rule_name = "firewall-sample-" + uuid.uuid4().hex[:10] + print(f"Creating firewall rule {rule_name}...") + # The rule will be created with default priority of 1000. + create_firewall_rule(default_project_id, rule_name) + try: + print("Rule created:") + print(get_firewall_rule(default_project_id, rule_name)) + print("Updating rule priority to 10...") + patch_firewall_priority(default_project_id, rule_name, 10) + print("Rule updated: ") + print(get_firewall_rule(default_project_id, rule_name)) + print(f"Deleting rule {rule_name}...") + finally: + delete_firewall_rule(default_project_id, rule_name) + print("Done.") diff --git a/compute/client_library/recipes/firewall/patch.py b/compute/client_library/recipes/firewall/patch.py new file mode 100644 index 00000000000..48b7173089e --- /dev/null +++ b/compute/client_library/recipes/firewall/patch.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# diff --git a/compute/client_library/recipes/firewall/windows_kms.py b/compute/client_library/recipes/firewall/windows_kms.py new file mode 100644 index 00000000000..f0948a35e0c --- /dev/null +++ b/compute/client_library/recipes/firewall/windows_kms.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/images/__init__.py b/compute/client_library/recipes/images/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/recipes/images/create.py b/compute/client_library/recipes/images/create.py new file mode 100644 index 00000000000..22c0d19cc50 --- /dev/null +++ b/compute/client_library/recipes/images/create.py @@ -0,0 +1,24 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# +# + +# + +# +# +# diff --git a/compute/client_library/recipes/images/create_from_image.py b/compute/client_library/recipes/images/create_from_image.py new file mode 100644 index 00000000000..d1e56b4419e --- /dev/null +++ b/compute/client_library/recipes/images/create_from_image.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# diff --git a/compute/client_library/recipes/images/create_from_snapshot.py b/compute/client_library/recipes/images/create_from_snapshot.py new file mode 100644 index 00000000000..a64f33fe31b --- /dev/null +++ b/compute/client_library/recipes/images/create_from_snapshot.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# diff --git a/compute/client_library/recipes/images/delete.py b/compute/client_library/recipes/images/delete.py new file mode 100644 index 00000000000..6796dae9eea --- /dev/null +++ b/compute/client_library/recipes/images/delete.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# diff --git a/compute/client_library/recipes/images/get.py b/compute/client_library/recipes/images/get.py new file mode 100644 index 00000000000..4524e8f2f22 --- /dev/null +++ b/compute/client_library/recipes/images/get.py @@ -0,0 +1,28 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# +# +# + + +# +# + + +# +# +# diff --git a/compute/client_library/recipes/images/list.py b/compute/client_library/recipes/images/list.py new file mode 100644 index 00000000000..80d3074cdc3 --- /dev/null +++ b/compute/client_library/recipes/images/list.py @@ -0,0 +1,20 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# +# diff --git a/compute/client_library/recipes/images/pagination.py b/compute/client_library/recipes/images/pagination.py new file mode 100644 index 00000000000..aa58b4f86a3 --- /dev/null +++ b/compute/client_library/recipes/images/pagination.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# +import google.cloud.compute_v1 as compute_v1 + +# +# + + +# +def print_images_list(project: str) -> str: + """ + Prints a list of all non-deprecated image names available in given project. + + Args: + project: project ID or project number of the Cloud project you want to list images from. + + Returns: + The output as a string. + """ + images_client = compute_v1.ImagesClient() + # Listing only non-deprecated images to reduce the size of the reply. + images_list_request = compute_v1.ListImagesRequest( + project=project, max_results=100, filter="deprecated.state != DEPRECATED" + ) + output = [] + + # Although the `max_results` parameter is specified in the request, the iterable returned + # by the `list()` method hides the pagination mechanic. The library makes multiple + # requests to the API for you, so you can simply iterate over all the images. + for img in images_client.list(request=images_list_request): + print(f" - {img.name}") + output.append(f" - {img.name}") + return "\n".join(output) + + +# + + +# +def print_images_list_by_page(project: str, page_size: int = 10) -> str: + """ + Prints a list of all non-deprecated image names available in a given project, + divided into pages as returned by the Compute Engine API. + + Args: + project: project ID or project number of the Cloud project you want to list images from. + page_size: size of the pages you want the API to return on each call. + + Returns: + Output as a string. + """ + images_client = compute_v1.ImagesClient() + # Listing only non-deprecated images to reduce the size of the reply. + images_list_request = compute_v1.ListImagesRequest( + project=project, max_results=page_size, filter="deprecated.state != DEPRECATED" + ) + output = [] + + # Use the `pages` attribute of returned iterable to have more granular control of + # iteration over paginated results from the API. Each time you want to access the + # next page, the library retrieves that page from the API. + for page_num, page in enumerate( + images_client.list(request=images_list_request).pages, start=1 + ): + print(f"Page {page_num}: ") + output.append(f"Page {page_num}: ") + for img in page.items: + print(f" - {img.name}") + output.append(f" - {img.name}") + return "\n".join(output) + + +# + + +if __name__ == "__main__": + print("=================== Flat list of images ===================") + print_images_list("windows-sql-cloud") + print("================= Paginated list of images ================") + print_images_list_by_page("windows-sql-cloud", 5) diff --git a/compute/client_library/recipes/images/set_deprecation_status.py b/compute/client_library/recipes/images/set_deprecation_status.py new file mode 100644 index 00000000000..e92d5254211 --- /dev/null +++ b/compute/client_library/recipes/images/set_deprecation_status.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# + diff --git a/compute/client_library/recipes/instance_templates/__init__.py b/compute/client_library/recipes/instance_templates/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/recipes/instance_templates/create.py b/compute/client_library/recipes/instance_templates/create.py new file mode 100644 index 00000000000..f3fe9d7971f --- /dev/null +++ b/compute/client_library/recipes/instance_templates/create.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# diff --git a/compute/client_library/recipes/instance_templates/create_from_instance.py b/compute/client_library/recipes/instance_templates/create_from_instance.py new file mode 100644 index 00000000000..2059793f321 --- /dev/null +++ b/compute/client_library/recipes/instance_templates/create_from_instance.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# diff --git a/compute/client_library/recipes/instance_templates/create_with_subnet.py b/compute/client_library/recipes/instance_templates/create_with_subnet.py new file mode 100644 index 00000000000..0aeba05dd1f --- /dev/null +++ b/compute/client_library/recipes/instance_templates/create_with_subnet.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# diff --git a/compute/client_library/recipes/instance_templates/delete.py b/compute/client_library/recipes/instance_templates/delete.py new file mode 100644 index 00000000000..8a9743b613b --- /dev/null +++ b/compute/client_library/recipes/instance_templates/delete.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# diff --git a/compute/client_library/recipes/instance_templates/get.py b/compute/client_library/recipes/instance_templates/get.py new file mode 100644 index 00000000000..3036e73488a --- /dev/null +++ b/compute/client_library/recipes/instance_templates/get.py @@ -0,0 +1,20 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# +# diff --git a/compute/client_library/recipes/instance_templates/list.py b/compute/client_library/recipes/instance_templates/list.py new file mode 100644 index 00000000000..6ce5c5b73c0 --- /dev/null +++ b/compute/client_library/recipes/instance_templates/list.py @@ -0,0 +1,20 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# +# diff --git a/compute/client_library/recipes/instances/__init__.py b/compute/client_library/recipes/instances/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/recipes/instances/bulk_insert.py b/compute/client_library/recipes/instances/bulk_insert.py new file mode 100644 index 00000000000..f5bfda9617a --- /dev/null +++ b/compute/client_library/recipes/instances/bulk_insert.py @@ -0,0 +1,40 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# + + +def create_five_instances(project_id: str, zone: str, template_name: str, + name_pattern: str): + """ + Create five instances of an instance template. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + template_name: name of the template that will be used to create new VMs. + name_pattern: The string pattern used for the names of the VMs. + """ + template = get_instance_template(project_id, template_name) + instances = bulk_insert_instance(project_id, zone, template, 5, name_pattern) + return instances +# diff --git a/compute/client_library/recipes/instances/create.py b/compute/client_library/recipes/instances/create.py new file mode 100644 index 00000000000..3f35a1e4c5a --- /dev/null +++ b/compute/client_library/recipes/instances/create.py @@ -0,0 +1,50 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# + +# +# + +if __name__ == "__main__": + import uuid + import google.auth + import google.auth.exceptions + + try: + default_project_id = google.auth.default()[1] + except google.auth.exceptions.DefaultCredentialsError: + print( + "Please use `gcloud auth application-default login` " + "or set GOOGLE_APPLICATION_CREDENTIALS to use this script." + ) + else: + instance_name = "quickstart-" + uuid.uuid4().hex[:10] + instance_zone = "europe-central2-b" + + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-10" + ) + disk_type = f"zones/{instance_zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + + create_instance(default_project_id, instance_zone, instance_name, disks) diff --git a/compute/client_library/recipes/instances/create_start_instance/__init__.py b/compute/client_library/recipes/instances/create_start_instance/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/recipes/instances/create_start_instance/create_from_custom_image.py b/compute/client_library/recipes/instances/create_start_instance/create_from_custom_image.py new file mode 100644 index 00000000000..a093f8cb31c --- /dev/null +++ b/compute/client_library/recipes/instances/create_start_instance/create_from_custom_image.py @@ -0,0 +1,31 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/create_start_instance/create_from_public_image.py b/compute/client_library/recipes/instances/create_start_instance/create_from_public_image.py new file mode 100644 index 00000000000..ac6f2d9bdfb --- /dev/null +++ b/compute/client_library/recipes/instances/create_start_instance/create_from_public_image.py @@ -0,0 +1,31 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/create_start_instance/create_from_snapshot.py b/compute/client_library/recipes/instances/create_start_instance/create_from_snapshot.py new file mode 100644 index 00000000000..34d3e9a9bab --- /dev/null +++ b/compute/client_library/recipes/instances/create_start_instance/create_from_snapshot.py @@ -0,0 +1,28 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/create_start_instance/create_windows_instance.py b/compute/client_library/recipes/instances/create_start_instance/create_windows_instance.py new file mode 100644 index 00000000000..53f565600e1 --- /dev/null +++ b/compute/client_library/recipes/instances/create_start_instance/create_windows_instance.py @@ -0,0 +1,33 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# +# + +# + + +# + +# + + +# + + +# +# +# diff --git a/compute/client_library/recipes/instances/create_start_instance/create_with_additional_disk.py b/compute/client_library/recipes/instances/create_start_instance/create_with_additional_disk.py new file mode 100644 index 00000000000..097cc1cf244 --- /dev/null +++ b/compute/client_library/recipes/instances/create_start_instance/create_with_additional_disk.py @@ -0,0 +1,35 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# +# + +# + + +# + + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/create_start_instance/create_with_existing_disks.py b/compute/client_library/recipes/instances/create_start_instance/create_with_existing_disks.py new file mode 100644 index 00000000000..dfa59eb3646 --- /dev/null +++ b/compute/client_library/recipes/instances/create_start_instance/create_with_existing_disks.py @@ -0,0 +1,29 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# +# + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/create_start_instance/create_with_local_ssd.py b/compute/client_library/recipes/instances/create_start_instance/create_with_local_ssd.py new file mode 100644 index 00000000000..853a58e1686 --- /dev/null +++ b/compute/client_library/recipes/instances/create_start_instance/create_with_local_ssd.py @@ -0,0 +1,32 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.py b/compute/client_library/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.py new file mode 100644 index 00000000000..19aec72dc13 --- /dev/null +++ b/compute/client_library/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.py @@ -0,0 +1,35 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# +# + +# + + +# + + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/create_with_subnet.py b/compute/client_library/recipes/instances/create_with_subnet.py new file mode 100644 index 00000000000..989e6448360 --- /dev/null +++ b/compute/client_library/recipes/instances/create_with_subnet.py @@ -0,0 +1,31 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/custom_hostname/create.py b/compute/client_library/recipes/instances/custom_hostname/create.py new file mode 100644 index 00000000000..588c603827b --- /dev/null +++ b/compute/client_library/recipes/instances/custom_hostname/create.py @@ -0,0 +1,31 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/custom_hostname/get.py b/compute/client_library/recipes/instances/custom_hostname/get.py new file mode 100644 index 00000000000..d69cce045fa --- /dev/null +++ b/compute/client_library/recipes/instances/custom_hostname/get.py @@ -0,0 +1,20 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# +# diff --git a/compute/client_library/recipes/instances/custom_machine_types/__init__.py b/compute/client_library/recipes/instances/custom_machine_types/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/recipes/instances/custom_machine_types/create_shared_with_helper.py b/compute/client_library/recipes/instances/custom_machine_types/create_shared_with_helper.py new file mode 100644 index 00000000000..a4d355d4dd8 --- /dev/null +++ b/compute/client_library/recipes/instances/custom_machine_types/create_shared_with_helper.py @@ -0,0 +1,34 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# + + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/custom_machine_types/create_with_helper.py b/compute/client_library/recipes/instances/custom_machine_types/create_with_helper.py new file mode 100644 index 00000000000..8619e68227f --- /dev/null +++ b/compute/client_library/recipes/instances/custom_machine_types/create_with_helper.py @@ -0,0 +1,36 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + + +# + + +# + + +# + +# + + +# + + +# + +# diff --git a/compute/client_library/recipes/instances/custom_machine_types/create_without_helper.py b/compute/client_library/recipes/instances/custom_machine_types/create_without_helper.py new file mode 100644 index 00000000000..6b99722f296 --- /dev/null +++ b/compute/client_library/recipes/instances/custom_machine_types/create_without_helper.py @@ -0,0 +1,31 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/custom_machine_types/extra_mem_no_helper.py b/compute/client_library/recipes/instances/custom_machine_types/extra_mem_no_helper.py new file mode 100644 index 00000000000..3b998965d70 --- /dev/null +++ b/compute/client_library/recipes/instances/custom_machine_types/extra_mem_no_helper.py @@ -0,0 +1,31 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/custom_machine_types/helper_class.py b/compute/client_library/recipes/instances/custom_machine_types/helper_class.py new file mode 100644 index 00000000000..e5a48c0c962 --- /dev/null +++ b/compute/client_library/recipes/instances/custom_machine_types/helper_class.py @@ -0,0 +1,20 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# +# diff --git a/compute/client_library/recipes/instances/custom_machine_types/update_memory.py b/compute/client_library/recipes/instances/custom_machine_types/update_memory.py new file mode 100644 index 00000000000..4d6b2ced889 --- /dev/null +++ b/compute/client_library/recipes/instances/custom_machine_types/update_memory.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# diff --git a/compute/client_library/recipes/instances/delete.py b/compute/client_library/recipes/instances/delete.py new file mode 100644 index 00000000000..99cff9e259c --- /dev/null +++ b/compute/client_library/recipes/instances/delete.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# +# diff --git a/compute/client_library/recipes/instances/delete_protection/__init__.py b/compute/client_library/recipes/instances/delete_protection/__init__.py new file mode 100644 index 00000000000..a3ded82a3b6 --- /dev/null +++ b/compute/client_library/recipes/instances/delete_protection/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa diff --git a/compute/client_library/recipes/instances/delete_protection/create.py b/compute/client_library/recipes/instances/delete_protection/create.py new file mode 100644 index 00000000000..c45d757eae1 --- /dev/null +++ b/compute/client_library/recipes/instances/delete_protection/create.py @@ -0,0 +1,31 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/delete_protection/get.py b/compute/client_library/recipes/instances/delete_protection/get.py new file mode 100644 index 00000000000..1d7697dd103 --- /dev/null +++ b/compute/client_library/recipes/instances/delete_protection/get.py @@ -0,0 +1,20 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# +# diff --git a/compute/client_library/recipes/instances/delete_protection/set.py b/compute/client_library/recipes/instances/delete_protection/set.py new file mode 100644 index 00000000000..ebd5b5970a3 --- /dev/null +++ b/compute/client_library/recipes/instances/delete_protection/set.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# diff --git a/compute/client_library/recipes/instances/from_instance_template/__init__.py b/compute/client_library/recipes/instances/from_instance_template/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/recipes/instances/from_instance_template/create_from_template.py b/compute/client_library/recipes/instances/from_instance_template/create_from_template.py new file mode 100644 index 00000000000..9a07cd10c36 --- /dev/null +++ b/compute/client_library/recipes/instances/from_instance_template/create_from_template.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# diff --git a/compute/client_library/recipes/instances/from_instance_template/create_from_template_with_overrides.py b/compute/client_library/recipes/instances/from_instance_template/create_from_template_with_overrides.py new file mode 100644 index 00000000000..c4d5ca1094f --- /dev/null +++ b/compute/client_library/recipes/instances/from_instance_template/create_from_template_with_overrides.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# diff --git a/compute/client_library/recipes/instances/get.py b/compute/client_library/recipes/instances/get.py new file mode 100644 index 00000000000..2dcc5c8849e --- /dev/null +++ b/compute/client_library/recipes/instances/get.py @@ -0,0 +1,20 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# +# diff --git a/compute/client_library/recipes/instances/list.py b/compute/client_library/recipes/instances/list.py new file mode 100644 index 00000000000..92aff46b1f5 --- /dev/null +++ b/compute/client_library/recipes/instances/list.py @@ -0,0 +1,20 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# +# diff --git a/compute/client_library/recipes/instances/list_all.py b/compute/client_library/recipes/instances/list_all.py new file mode 100644 index 00000000000..e1fafd7f2aa --- /dev/null +++ b/compute/client_library/recipes/instances/list_all.py @@ -0,0 +1,20 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# +# diff --git a/compute/client_library/recipes/instances/preemptible/__init__.py b/compute/client_library/recipes/instances/preemptible/__init__.py new file mode 100644 index 00000000000..a3ded82a3b6 --- /dev/null +++ b/compute/client_library/recipes/instances/preemptible/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa diff --git a/compute/client_library/recipes/instances/preemptible/create_preemptible.py b/compute/client_library/recipes/instances/preemptible/create_preemptible.py new file mode 100644 index 00000000000..18791d35d5e --- /dev/null +++ b/compute/client_library/recipes/instances/preemptible/create_preemptible.py @@ -0,0 +1,31 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/preemptible/is_preemptible.py b/compute/client_library/recipes/instances/preemptible/is_preemptible.py new file mode 100644 index 00000000000..d57031c83fe --- /dev/null +++ b/compute/client_library/recipes/instances/preemptible/is_preemptible.py @@ -0,0 +1,21 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# diff --git a/compute/client_library/recipes/instances/preemptible/preemption_history.py b/compute/client_library/recipes/instances/preemptible/preemption_history.py new file mode 100644 index 00000000000..0c9a9a8ce6d --- /dev/null +++ b/compute/client_library/recipes/instances/preemptible/preemption_history.py @@ -0,0 +1,24 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# + +# diff --git a/compute/client_library/recipes/instances/reset.py b/compute/client_library/recipes/instances/reset.py new file mode 100644 index 00000000000..ce09f66e79b --- /dev/null +++ b/compute/client_library/recipes/instances/reset.py @@ -0,0 +1,22 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# +# diff --git a/compute/client_library/recipes/instances/resume.py b/compute/client_library/recipes/instances/resume.py new file mode 100644 index 00000000000..4cc8d7ef5d9 --- /dev/null +++ b/compute/client_library/recipes/instances/resume.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# +# diff --git a/compute/client_library/recipes/instances/spot/__init__.py b/compute/client_library/recipes/instances/spot/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/recipes/instances/spot/create.py b/compute/client_library/recipes/instances/spot/create.py new file mode 100644 index 00000000000..fba16e4ce8a --- /dev/null +++ b/compute/client_library/recipes/instances/spot/create.py @@ -0,0 +1,31 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# + +# + + +# + + +# +# diff --git a/compute/client_library/recipes/instances/spot/is_spot_vm.py b/compute/client_library/recipes/instances/spot/is_spot_vm.py new file mode 100644 index 00000000000..ea613650ab0 --- /dev/null +++ b/compute/client_library/recipes/instances/spot/is_spot_vm.py @@ -0,0 +1,21 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# diff --git a/compute/client_library/recipes/instances/start.py b/compute/client_library/recipes/instances/start.py new file mode 100644 index 00000000000..913680aa9fa --- /dev/null +++ b/compute/client_library/recipes/instances/start.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# +# diff --git a/compute/client_library/recipes/instances/start_encrypted.py b/compute/client_library/recipes/instances/start_encrypted.py new file mode 100644 index 00000000000..d6a0194bd5a --- /dev/null +++ b/compute/client_library/recipes/instances/start_encrypted.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# +# diff --git a/compute/client_library/recipes/instances/stop.py b/compute/client_library/recipes/instances/stop.py new file mode 100644 index 00000000000..49abe79a036 --- /dev/null +++ b/compute/client_library/recipes/instances/stop.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# +# diff --git a/compute/client_library/recipes/instances/suspend.py b/compute/client_library/recipes/instances/suspend.py new file mode 100644 index 00000000000..59c5b75f091 --- /dev/null +++ b/compute/client_library/recipes/instances/suspend.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + + +# +# diff --git a/compute/client_library/recipes/operations/__init__.py b/compute/client_library/recipes/operations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/recipes/operations/operation_check.py b/compute/client_library/recipes/operations/operation_check.py new file mode 100644 index 00000000000..8913e7324cc --- /dev/null +++ b/compute/client_library/recipes/operations/operation_check.py @@ -0,0 +1,33 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# +# diff --git a/compute/client_library/recipes/routes/create.py b/compute/client_library/recipes/routes/create.py new file mode 100644 index 00000000000..6862af01252 --- /dev/null +++ b/compute/client_library/recipes/routes/create.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/routes/create_kms_route.py b/compute/client_library/recipes/routes/create_kms_route.py new file mode 100644 index 00000000000..ec693259e97 --- /dev/null +++ b/compute/client_library/recipes/routes/create_kms_route.py @@ -0,0 +1,57 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +def create_route_to_windows_activation_host(project_id: str, network: str, route_name: str) -> compute_v1.Route: + """ + If you have Windows instances without external IP addresses, + you must also enable Private Google Access so that instances + with only internal IP addresses can send traffic to the external + IP address for kms.windows.googlecloud.com. + More infromation: https://cloud.google.com/vpc/docs/configure-private-google-access#enabling + + Args: + project_id: project ID or project number of the Cloud project you want to use. + network: name of the network the route will be created in. Available name formats: + * https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network} + * projects/{project_id}/global/networks/{network} + * global/networks/{network} + route_name: name of the new route. + + Returns: + A new compute_v1.Route object. + """ + return create_route(project_id=project_id, network=network, route_name=route_name, + destination_range='35.190.247.13/32', + next_hop_gateway=f"projects/{project_id}/global/gateways/default-internet-gateway") +# diff --git a/compute/client_library/recipes/routes/delete.py b/compute/client_library/recipes/routes/delete.py new file mode 100644 index 00000000000..5ea48302b4b --- /dev/null +++ b/compute/client_library/recipes/routes/delete.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/routes/list.py b/compute/client_library/recipes/routes/list.py new file mode 100644 index 00000000000..3869f571b9c --- /dev/null +++ b/compute/client_library/recipes/routes/list.py @@ -0,0 +1,21 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# diff --git a/compute/client_library/recipes/snapshots/__init__.py b/compute/client_library/recipes/snapshots/__init__.py new file mode 100644 index 00000000000..4bbe0ffdb06 --- /dev/null +++ b/compute/client_library/recipes/snapshots/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/compute/client_library/recipes/snapshots/create.py b/compute/client_library/recipes/snapshots/create.py new file mode 100644 index 00000000000..5b54951b1d5 --- /dev/null +++ b/compute/client_library/recipes/snapshots/create.py @@ -0,0 +1,24 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# + diff --git a/compute/client_library/recipes/snapshots/delete.py b/compute/client_library/recipes/snapshots/delete.py new file mode 100644 index 00000000000..a9686d6d3c3 --- /dev/null +++ b/compute/client_library/recipes/snapshots/delete.py @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# + +# diff --git a/compute/client_library/recipes/snapshots/delete_by_filter.py b/compute/client_library/recipes/snapshots/delete_by_filter.py new file mode 100644 index 00000000000..d250c37a9d5 --- /dev/null +++ b/compute/client_library/recipes/snapshots/delete_by_filter.py @@ -0,0 +1,37 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# flake8: noqa + +# +# + +# + +# + +# + +def delete_snapshots_by_filter(project_id: str, filter: str): + """ + Deletes all snapshots in project that meet the filter criteria. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + filter: filter to be applied when looking for snapshots for deletion. + """ + for snapshot in list_snapshots(project_id, filter): + delete_snapshot(project_id, snapshot.name) + +# \ No newline at end of file diff --git a/compute/client_library/recipes/snapshots/get.py b/compute/client_library/recipes/snapshots/get.py new file mode 100644 index 00000000000..bb0a324aeb9 --- /dev/null +++ b/compute/client_library/recipes/snapshots/get.py @@ -0,0 +1,21 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# diff --git a/compute/client_library/recipes/snapshots/list.py b/compute/client_library/recipes/snapshots/list.py new file mode 100644 index 00000000000..34b5701da86 --- /dev/null +++ b/compute/client_library/recipes/snapshots/list.py @@ -0,0 +1,21 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +# +# + +# + +# diff --git a/compute/client_library/recipes/usage_report/__init__.py b/compute/client_library/recipes/usage_report/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/recipes/usage_report/usage_reports.py b/compute/client_library/recipes/usage_report/usage_reports.py new file mode 100644 index 00000000000..d153b48d9e3 --- /dev/null +++ b/compute/client_library/recipes/usage_report/usage_reports.py @@ -0,0 +1,51 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa +""" +A sample script showing how to handle default values when communicating +with the Compute Engine API and how to configure usage reports using the API. +""" +# +# +# +# +# + +# +# +# + + +# + +# + +# + +# + +# +# + +# +# + + +# + +# + +# + +# diff --git a/compute/client_library/requirements-test.txt b/compute/client_library/requirements-test.txt new file mode 100644 index 00000000000..1a35c12716c --- /dev/null +++ b/compute/client_library/requirements-test.txt @@ -0,0 +1,6 @@ +pytest==7.2.0 +pytest-parallel==0.1.1 +flaky==3.7.0 +google-cloud-storage==2.5.0 +google-cloud-kms==2.12.3 +py==1.11.0 diff --git a/compute/client_library/requirements.txt b/compute/client_library/requirements.txt new file mode 100644 index 00000000000..0de8929bd5e --- /dev/null +++ b/compute/client_library/requirements.txt @@ -0,0 +1,3 @@ +isort==5.10.1 +black==22.10.0 +google-cloud-compute==1.6.1 diff --git a/compute/client_library/sgs.py b/compute/client_library/sgs.py new file mode 100644 index 00000000000..e3fa70a8b94 --- /dev/null +++ b/compute/client_library/sgs.py @@ -0,0 +1,350 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This script is used to generate the full code samples inside the `snippets` +directory, to be then used in Google Compute Engine public documentation. +""" +import argparse +import ast +from collections import defaultdict +from dataclasses import dataclass +from dataclasses import field +import glob +import os +from pathlib import Path +import re +import subprocess +from typing import List, Tuple +import warnings + +import isort + +INGREDIENTS_START = re.compile(r"\s*#\s*") +INGREDIENTS_END = re.compile(r"\s*#\s*") + +IMPORTS_FILL = re.compile(r"\s*#\s*") +INGREDIENT_FILL = re.compile(r"\s*#\s*") + +REGION_START = re.compile(r"#\s*") +REGION_END = re.compile(r"#\s*") + +HEADER = """\ +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. +""" + +DEFAULT_OUTPUT_PATH = Path("snippets") +INGREDIENTS_PATH = Path("ingredients") +RECIPES_PATH = Path("recipes") + + +@dataclass +class ImportItem: + """ + Represents a single import item in a script, created either by + `import something as something_else` or + `from module import something as something_else`. + """ + + name: str + asname: str + + def __hash__(self): + return hash(f"{self.name} as {self.asname}") + + +@dataclass +class Ingredient: + """ + This class represents a piece of code that can be used as part of a code snippet. + Each ingredient has a name. It is made of a list of imports that it'll require and + text that will be pasted into the snippet. + """ + + simple_imports: List[ImportItem] = field(default_factory=list) + imports_from: List[Tuple[str, ImportItem]] = field(default_factory=list) + text: str = "" + name: str = "" + + def __repr__(self): + return f"" + + +IGNORED_OUTPUT_FILES = ( + re.compile(r".*noxfile\.py$"), + re.compile(r".*noxfile_config\.py$"), + re.compile(r".*README\.md$"), + re.compile(r".*requirements\.txt$"), + re.compile(r".*requirements-test\.txt$"), + re.compile(r".*?/tests/.*"), + re.compile(r".*?/__pycache__/.*"), + re.compile(r".*?sponge_log.xml.*"), +) + + +def parse_imports(script: str) -> Tuple[List[ImportItem], List[Tuple[str, ImportItem]]]: + """ + Reads a Python script file and analyzes it to extract information + about the various things it imports. Returns a pair of lists containing + information about the "simple imports" (`import abc as xyz`) and "imports from" + (`from collections import deque as ...`). + """ + parsed_script = ast.parse(script) + simple_imports = [] + imports_from = [] + for node in parsed_script.body: + if isinstance(node, ast.Import): + for alias in node.names: + simple_imports.append(ImportItem(name=alias.name, asname=alias.asname)) + elif isinstance(node, ast.ImportFrom): + for alias in node.names: + imports_from.append( + (node.module, ImportItem(name=alias.name, asname=alias.asname)) + ) + return simple_imports, imports_from + + +def load_ingredient(path: Path) -> Ingredient: + ingredient_lines = [] + in_ingredient = False + ingredient_name = "" + with path.open() as file: + file_content = file.read() + # Read imports + simple_imports, imports_from = parse_imports(file_content) + # Read the script + for line in file_content.splitlines(keepends=True): + if in_ingredient and INGREDIENTS_END.match(line): + break + elif in_ingredient: + ingredient_lines.append(line) + elif INGREDIENTS_START.match(line): + ingredient_name = INGREDIENTS_START.match(line).group(1) + in_ingredient = True + else: + if in_ingredient: + warnings.warn( + f"The ingredient in {path} has no closing tag.", SyntaxWarning + ) + return Ingredient( + name=ingredient_name, + text="".join(ingredient_lines), + simple_imports=simple_imports, + imports_from=imports_from, + ) + + +def load_ingredients(path: Path) -> dict: + ingredients = {} + for ipath in path.iterdir(): + if ipath.is_dir(): + ingredients.update(load_ingredients(ipath)) + elif ipath.is_file(): + if "__pycache__" in str(ipath.absolute()): + continue + ingredient = load_ingredient(ipath) + ingredients[ingredient.name] = ingredient + return ingredients + + +def load_recipe(path: Path) -> str: + with path.open() as file: + return file.read() + + +def load_recipes(path: Path) -> dict: + recipes = {} + for ipath in path.iterdir(): + if ipath.is_dir(): + recipes.update(load_recipes(ipath)) + elif ipath.is_file(): + recipes[ipath.absolute()] = load_recipe(ipath) + return recipes + + +def render_recipe(recipe: str, ingredients: dict) -> str: + """ + Replace all `# IMPORTS` and `# INGREDIENT ` occurrences in + the provided recipe, producing a script ready to be saved to a file. + """ + ingredients_used = [] + file_lines = recipe.splitlines() + + # Scan the file to used ingredients + for line in file_lines: + match = INGREDIENT_FILL.match(line) + if match: + ingredients_used.append(ingredients[match.group(1)]) + + simple_imports_used = set() + for ingredient in ingredients_used: + for simple_import in ingredient.simple_imports: + simple_imports_used.add(simple_import) + + from_imports_used = defaultdict(set) + for ingredient in ingredients_used: + for import_from in ingredient.imports_from: + from_imports_used[import_from[0]].add(import_from[1]) + + import_lines = set() + for simple_import in simple_imports_used: + if simple_import.asname: + import_lines.add(f"import {simple_import.name} as {simple_import.asname}") + else: + import_lines.add(f"import {simple_import.name}") + + for module, from_imports in from_imports_used.items(): + names = set() + for from_import in from_imports: + if from_import.asname: + name = f"{from_import.name} as {from_import.asname}" + else: + name = from_import.name + names.add(name) + names = ", ".join(names) + import_lines.add(f"from {module} import {names}") + + import_lines = isort.code( + "\n".join(import_lines), config=isort.Config(profile="google") + ) + + output_file = [] + header_added = False + for line in file_lines: + + if IMPORTS_FILL.search(line): + output_file.append(import_lines) + elif INGREDIENT_FILL.search(line): + match = INGREDIENT_FILL.search(line) + output_file.append(ingredients[match.group(1)].text) + elif REGION_START.search(line): + # The string has to be broken up, so that the snippet + # machine doesn't recognize it as a valid start of a region + output_file.append(REGION_START.sub("# [" + "START \\1]", line)) + elif REGION_END.search(line): + # The string has to be broken up, so that the snippet + # machine doesn't recognize it as a valid start of a region + output_file.append(REGION_END.sub("# [" + "END \\1]", line)) + else: + output_file.append(line) + continue + if not header_added: + end = output_file[-1] + output_file[-1] = "" + output_file.append(HEADER) + output_file.append("") + output_file.append(end) + header_added = True + + if output_file and not output_file[-1].endswith("\n"): + output_file.append("") + + return os.linesep.join(output_file) + + +def save_rendered_recipe( + recipe_path: Path, + rendered_recipe: str, + output_dir: Path = DEFAULT_OUTPUT_PATH, + recipes_path: Path = RECIPES_PATH, +) -> Path: + output_dir.mkdir(parents=True, exist_ok=True) + output_path = output_dir / recipe_path.relative_to(recipes_path) + output_path.parent.mkdir(parents=True, exist_ok=True) + + with output_path.open(mode="w") as out_file: + out_file.write(rendered_recipe) + + subprocess.run( + ["black", str(output_path)], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return output_path + + +def generate( + args: argparse.Namespace, + ingredients_path: Path = INGREDIENTS_PATH, + recipes_path: Path = RECIPES_PATH, +): + ingredients = load_ingredients(ingredients_path) + recipes = load_recipes(recipes_path) + + updated_paths = set() + + for path, recipe in recipes.items(): + rendered = render_recipe(recipe, ingredients) + out = save_rendered_recipe( + path.absolute(), + rendered, + recipes_path=recipes_path.absolute(), + output_dir=Path(args.output_dir), + ) + updated_paths.add(str(out)) + + print("Generated files:") + for file in sorted(updated_paths): + print(f" - {repr(file)}") + + all_files = glob.glob(f"{args.output_dir}/**", recursive=True) + unknown_files = set() + for file in all_files: + if file in updated_paths: + continue + if any(pattern.match(file) for pattern in IGNORED_OUTPUT_FILES): + continue + pfile = Path(file) + if pfile.is_dir() and pfile.iterdir(): + # Don't report non-empty dirs. + continue + unknown_files.add(file) + + if unknown_files: + print("Found following unknown files: ") + for file in sorted(unknown_files): + print(f" - {repr(file)}") + + +def verify(args: argparse.Namespace): + # TODO: Needs to check if the files are up to date. Will be used to auto-check every commit. + pass + + +def parse_arguments(): + parser = argparse.ArgumentParser( + description="Generates full code snippets from their recipes." + ) + subparsers = parser.add_subparsers() + + gen_parser = subparsers.add_parser("generate", help="Generates the code samples.") + gen_parser.set_defaults(func=generate) + gen_parser.add_argument("--output_dir", default=DEFAULT_OUTPUT_PATH) + + verify_parser = subparsers.add_parser( + "verify", help="Verify if the generated samples match the sources." + ) + verify_parser.set_defaults(func=verify) + + return parser.parse_args() + + +def main(): + args = parse_arguments() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/compute/client_library/sgs_test_fixtures/ingredients/ingredient1.pytest b/compute/client_library/sgs_test_fixtures/ingredients/ingredient1.pytest new file mode 100644 index 00000000000..c547d737447 --- /dev/null +++ b/compute/client_library/sgs_test_fixtures/ingredients/ingredient1.pytest @@ -0,0 +1,30 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import defaultdict +from functools import reduce +import pprint + + +# +def some_function(a: int, b: str) -> defaultdict: + """ + Do something with a and b that will give a defaultdict. + """ + out = defaultdict(int) + for letter in b: + out[letter] += a * ord(letter) + reduce(lambda x, y: x+ord(y), b, 0) + pprint.pprint(out) + return out +# \ diff --git a/compute/client_library/sgs_test_fixtures/ingredients/ingredient2.pytest b/compute/client_library/sgs_test_fixtures/ingredients/ingredient2.pytest new file mode 100644 index 00000000000..3b50b3b62b3 --- /dev/null +++ b/compute/client_library/sgs_test_fixtures/ingredients/ingredient2.pytest @@ -0,0 +1,31 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import Counter +from functools import cache +from functools import reduce + + +# +@cache +def other_function(word: str, number: int) -> Counter: + """ + Do something with the arguments. I don't care what. + """ + new_word = reduce(lambda s1, s2: s1 + s2 + s2, word, '') + letters = Counter(new_word) + for letter in word: + letters.update({letter: number*ord(letter)}) + return letters +# diff --git a/compute/client_library/sgs_test_fixtures/output/experimental_recipe.pytest b/compute/client_library/sgs_test_fixtures/output/experimental_recipe.pytest new file mode 100644 index 00000000000..f8846776f88 --- /dev/null +++ b/compute/client_library/sgs_test_fixtures/output/experimental_recipe.pytest @@ -0,0 +1,60 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +from collections import Counter +from collections import defaultdict +from functools import cache +from functools import reduce +import pprint + + +def some_function(a: int, b: str) -> defaultdict: + """ + Do something with a and b that will give a defaultdict. + """ + out = defaultdict(int) + for letter in b: + out[letter] += a * ord(letter) + reduce(lambda x, y: x + ord(y), b, 0) + pprint.pprint(out) + return out + + +# I can have some random things between ingredients +def test(): + print("This is a test. The only thing I shouldn't place in recipes is imports.") + + +@cache +def other_function(word: str, number: int) -> Counter: + """ + Do something with the arguments. I don't care what. + """ + new_word = reduce(lambda s1, s2: s1 + s2 + s2, word, "") + letters = Counter(new_word) + for letter in word: + letters.update({letter: number * ord(letter)}) + return letters + + +if __name__ == "__main__": + print("Here is an example of two functions:") + some_function(14, "google") + other_function("google", 9001) + print("That's it :)") diff --git a/compute/client_library/sgs_test_fixtures/recipes/experimental_recipe.pytest b/compute/client_library/sgs_test_fixtures/recipes/experimental_recipe.pytest new file mode 100644 index 00000000000..6f384ed4f79 --- /dev/null +++ b/compute/client_library/sgs_test_fixtures/recipes/experimental_recipe.pytest @@ -0,0 +1,29 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# + +# + +# I can have some random things between ingredients +def test(): + print("This is a test. The only thing I shouldn't place in recipes is imports.") + +# + +if __name__ == '__main__': + print("Here is an example of two functions:") + some_function(14, "google") + other_function("google", 9001) + print("That's it :)") diff --git a/compute/client_library/snippets/README.md b/compute/client_library/snippets/README.md new file mode 100644 index 00000000000..64d0ec565fd --- /dev/null +++ b/compute/client_library/snippets/README.md @@ -0,0 +1,31 @@ +# google-cloud-compute library samples + +These samples demonstrate usage of the google-cloud-compute library to interact +with the Google Compute Engine API. + +## Running the quickstart script + +### Before you begin + +1. If you haven't already, set up a Python Development Environment by following the [python setup guide](https://cloud.google.com/python/setup) and +[create a project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project). + +1. Use `gcloud auth application-default login` to allow the script to authenticate using +your credentials to the Google Cloud APIs. + +### Install requirements + +Create a new virtual environment and install the required libraries. +```bash +virtualenv --python python3 name-of-your-virtualenv +source name-of-your-virtualenv/bin/activate +pip install -r ../requirements.txt +``` + +### Run the demo + +Run the quickstart script, it will create and destroy a `n1-standard-1` +type machine in the `europe-central2-b` zone. +```bash +python quickstart.py +``` diff --git a/compute/client_library/snippets/__init__.py b/compute/client_library/snippets/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/snippets/disks/__init__.py b/compute/client_library/snippets/disks/__init__.py new file mode 100644 index 00000000000..4bbe0ffdb06 --- /dev/null +++ b/compute/client_library/snippets/disks/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/compute/client_library/snippets/disks/autodelete_change.py b/compute/client_library/snippets/disks/autodelete_change.py new file mode 100644 index 00000000000..bb903a7268b --- /dev/null +++ b/compute/client_library/snippets/disks/autodelete_change.py @@ -0,0 +1,116 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_disk_autodelete_change] +import sys +from typing import Any, NoReturn + +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: + """ + This method will wait 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 set_disk_autodelete( + project_id: str, zone: str, instance_name: str, disk_name: str, autodelete: bool +) -> NoReturn: + """ + Set the autodelete flag of a disk to given value. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone in which is the disk you want to modify. + instance_name: name of the instance the disk is attached to. + disk_name: the name of the disk which flag you want to modify. + autodelete: the new value of the autodelete flag. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + + for disk in instance.disks: + if disk.device_name == disk_name: + break + else: + raise RuntimeError( + f"Instance {instance_name} doesn't have a disk named {disk_name} attached." + ) + + disk.auto_delete = autodelete + + operation = instance_client.update( + project=project_id, + zone=zone, + instance=instance_name, + instance_resource=instance, + ) + + wait_for_extended_operation(operation, "disk update") + return + + +# [END compute_disk_autodelete_change] diff --git a/compute/client_library/snippets/disks/clone_encrypted_disk.py b/compute/client_library/snippets/disks/clone_encrypted_disk.py new file mode 100644 index 00000000000..5850656e2b8 --- /dev/null +++ b/compute/client_library/snippets/disks/clone_encrypted_disk.py @@ -0,0 +1,124 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_disk_clone_encrypted_disk] +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: + """ + This method will wait 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) + + +# [END compute_disk_clone_encrypted_disk] diff --git a/compute/client_library/snippets/disks/clone_encrypted_disk_managed_key.py b/compute/client_library/snippets/disks/clone_encrypted_disk_managed_key.py new file mode 100644 index 00000000000..8f89d191be5 --- /dev/null +++ b/compute/client_library/snippets/disks/clone_encrypted_disk_managed_key.py @@ -0,0 +1,125 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_disk_clone_encrypted_disk_kms] +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: + """ + This method will wait 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. + + 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 + 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) + + +# [END compute_disk_clone_encrypted_disk_kms] diff --git a/compute/client_library/snippets/disks/create_empty_disk.py b/compute/client_library/snippets/disks/create_empty_disk.py new file mode 100644 index 00000000000..331f0b21283 --- /dev/null +++ b/compute/client_library/snippets/disks/create_empty_disk.py @@ -0,0 +1,109 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_disk_create_empty_disk] +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: + """ + This method will wait 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_empty_disk( + project_id: str, zone: str, disk_name: str, disk_type: str, disk_size_gb: int +) -> compute_v1.Disk: + """ + Creates a new empty disk in a project in 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 + + Returns: + An unattached Disk instance. + """ + disk = compute_v1.Disk() + disk.size_gb = disk_size_gb + disk.name = disk_name + disk.zone = zone + disk.type_ = disk_type + + disk_client = compute_v1.DisksClient() + 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) + + +# [END compute_disk_create_empty_disk] diff --git a/compute/client_library/snippets/disks/create_from_image.py b/compute/client_library/snippets/disks/create_from_image.py new file mode 100644 index 00000000000..0b8e17ea060 --- /dev/null +++ b/compute/client_library/snippets/disks/create_from_image.py @@ -0,0 +1,118 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_disk_create_from_image] +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: + """ + This method will wait 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_image( + project_id: str, + zone: str, + disk_name: str, + disk_type: str, + disk_size_gb: int, + source_image: str, +) -> compute_v1.Disk: + """ + Creates a new disk in a project in given zone using an image as base. + + 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 + source_image: source image to use when creating this disk. You must have read access to this disk. This + can be one of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + + Returns: + An unattached Disk instance. + """ + disk = compute_v1.Disk() + disk.size_gb = disk_size_gb + disk.name = disk_name + disk.zone = zone + disk.type_ = disk_type + disk.source_image = source_image + + disk_client = compute_v1.DisksClient() + 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) + + +# [END compute_disk_create_from_image] diff --git a/compute/client_library/snippets/disks/create_from_snapshot.py b/compute/client_library/snippets/disks/create_from_snapshot.py new file mode 100644 index 00000000000..deb0f9dfd5f --- /dev/null +++ b/compute/client_library/snippets/disks/create_from_snapshot.py @@ -0,0 +1,116 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_disk_create_from_snapshot] +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: + """ + This method will wait 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_snapshot( + project_id: str, + zone: str, + disk_name: str, + disk_type: str, + disk_size_gb: int, + snapshot_link: str, +) -> compute_v1.Disk: + """ + Creates a new disk in a project in 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 + 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 unattached Disk instance. + """ + disk_client = compute_v1.DisksClient() + disk = compute_v1.Disk() + disk.zone = zone + disk.size_gb = disk_size_gb + disk.source_snapshot = snapshot_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) + + +# [END compute_disk_create_from_snapshot] diff --git a/compute/client_library/snippets/disks/create_from_source.py b/compute/client_library/snippets/disks/create_from_source.py new file mode 100644 index 00000000000..85a1d38254e --- /dev/null +++ b/compute/client_library/snippets/disks/create_from_source.py @@ -0,0 +1,116 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_disk_create_from_disk] +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: + """ + This method will wait 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) + + +# [END compute_disk_create_from_disk] diff --git a/compute/client_library/snippets/disks/create_kms_encrypted_disk.py b/compute/client_library/snippets/disks/create_kms_encrypted_disk.py new file mode 100644 index 00000000000..d3f64377a16 --- /dev/null +++ b/compute/client_library/snippets/disks/create_kms_encrypted_disk.py @@ -0,0 +1,130 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_create_kms_encrypted_disk] +import sys +from typing import Any, Optional + +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: + """ + This method will wait 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_kms_encrypted_disk( + project_id: str, + zone: str, + disk_name: str, + disk_type: str, + disk_size_gb: int, + kms_key_name: str, + disk_link: Optional[str] = None, + image_link: Optional[str] = None, +) -> compute_v1.Disk: + """ + Creates a zonal disk in a project. If you do not provide values for disk_link or image_link, + an empty disk will be created. + + 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 + 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}" + 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}" + image_link: a link to the image you want to use as a source for the new disk. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + + Returns: + An attachable disk. + """ + disk_client = compute_v1.DisksClient() + disk = compute_v1.Disk() + disk.zone = zone + disk.size_gb = disk_size_gb + if disk_link: + disk.source_disk = disk_link + if image_link: + disk.source_image = image_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 + 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) + + +# [END compute_create_kms_encrypted_disk] diff --git a/compute/client_library/snippets/disks/delete.py b/compute/client_library/snippets/disks/delete.py new file mode 100644 index 00000000000..c396911eb21 --- /dev/null +++ b/compute/client_library/snippets/disks/delete.py @@ -0,0 +1,92 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_disk_delete] +import sys +from typing import Any, NoReturn + +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: + """ + This method will wait 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 delete_disk(project_id: str, zone: str, disk_name: str) -> NoReturn: + """ + Deletes a disk from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone in which is the disk you want to delete. + disk_name: name of the disk you want to delete. + """ + disk_client = compute_v1.DisksClient() + operation = disk_client.delete(project=project_id, zone=zone, disk=disk_name) + wait_for_extended_operation(operation, "disk deletion") + return + + +# [END compute_disk_delete] diff --git a/compute/client_library/snippets/disks/list.py b/compute/client_library/snippets/disks/list.py new file mode 100644 index 00000000000..2b522e2b982 --- /dev/null +++ b/compute/client_library/snippets/disks/list.py @@ -0,0 +1,49 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_disk_list] +import sys +from typing import Iterable, NoReturn + +from google.cloud import compute_v1 + + +def list_disks( + project_id: str, zone: str, filter_: str = "" +) -> Iterable[compute_v1.Disk]: + """ + Deletes a disk from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone in which is the disk you want to delete. + filter_: filter to be applied when listing disks. Learn more about filters here: + https://cloud.google.com/python/docs/reference/compute/latest/google.cloud.compute_v1.types.ListDisksRequest + """ + disk_client = compute_v1.DisksClient() + request = compute_v1.ListDisksRequest() + request.project = project_id + request.zone = zone + request.filter = filter_ + return disk_client.list(request) + + +# [END compute_disk_list] diff --git a/compute/client_library/snippets/disks/regional_create_from_source.py b/compute/client_library/snippets/disks/regional_create_from_source.py new file mode 100644 index 00000000000..25a682b391c --- /dev/null +++ b/compute/client_library/snippets/disks/regional_create_from_source.py @@ -0,0 +1,129 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_regional_disk_create_from_disk] +import sys +from typing import Any, Iterable, Optional + +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: + """ + This method will wait 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: Optional[str] = None, + snapshot_link: Optional[str] = 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) + + +# [END compute_regional_disk_create_from_disk] diff --git a/compute/client_library/snippets/disks/regional_delete.py b/compute/client_library/snippets/disks/regional_delete.py new file mode 100644 index 00000000000..b68bbd96981 --- /dev/null +++ b/compute/client_library/snippets/disks/regional_delete.py @@ -0,0 +1,92 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_regional_disk_delete] +import sys +from typing import Any, NoReturn + +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: + """ + This method will wait 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 delete_regional_disk(project_id: str, region: str, disk_name: str) -> NoReturn: + """ + Deletes a disk from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + region:name of the region where the disk is located. + disk_name: name of the disk that you want to delete. + """ + disk_client = compute_v1.RegionDisksClient() + operation = disk_client.delete(project=project_id, region=region, disk=disk_name) + wait_for_extended_operation(operation, "regional disk deletion") + return + + +# [END compute_regional_disk_delete] diff --git a/compute/client_library/snippets/firewall/__init__.py b/compute/client_library/snippets/firewall/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/snippets/firewall/create.py b/compute/client_library/snippets/firewall/create.py new file mode 100644 index 00000000000..bcdacb55920 --- /dev/null +++ b/compute/client_library/snippets/firewall/create.py @@ -0,0 +1,127 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_firewall_create] +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: + """ + This method will wait 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_firewall_rule( + project_id: str, firewall_rule_name: str, network: str = "global/networks/default" +) -> compute_v1.Firewall: + """ + Creates a simple firewall rule allowing for incoming HTTP and HTTPS access from the entire Internet. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + firewall_rule_name: name of the rule that is created. + network: name of the network the rule will be applied to. Available name formats: + * https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network} + * projects/{project_id}/global/networks/{network} + * global/networks/{network} + + Returns: + A Firewall object. + """ + firewall_rule = compute_v1.Firewall() + firewall_rule.name = firewall_rule_name + firewall_rule.direction = "INGRESS" + + allowed_ports = compute_v1.Allowed() + allowed_ports.I_p_protocol = "tcp" + allowed_ports.ports = ["80", "443"] + + firewall_rule.allowed = [allowed_ports] + firewall_rule.source_ranges = ["0.0.0.0/0"] + firewall_rule.network = network + firewall_rule.description = "Allowing TCP traffic on port 80 and 443 from Internet." + + firewall_rule.target_tags = ["web"] + + # Note that the default value of priority for the firewall API is 1000. + # If you check the value of `firewall_rule.priority` at this point it + # will be equal to 0, however it is not treated as "set" by the library and thus + # the default will be applied to the new rule. If you want to create a rule that + # has priority == 0, you need to explicitly set it so: + # TODO: Uncomment to set the priority to 0 + # firewall_rule.priority = 0 + + firewall_client = compute_v1.FirewallsClient() + operation = firewall_client.insert( + project=project_id, firewall_resource=firewall_rule + ) + + wait_for_extended_operation(operation, "firewall rule creation") + + return firewall_client.get(project=project_id, firewall=firewall_rule_name) + + +# [END compute_firewall_create] diff --git a/compute/client_library/snippets/firewall/delete.py b/compute/client_library/snippets/firewall/delete.py new file mode 100644 index 00000000000..f645f618d17 --- /dev/null +++ b/compute/client_library/snippets/firewall/delete.py @@ -0,0 +1,92 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_firewall_delete] +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: + """ + This method will wait 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 delete_firewall_rule(project_id: str, firewall_rule_name: str) -> None: + """ + Deletes a firewall rule from the project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + firewall_rule_name: name of the firewall rule you want to delete. + """ + firewall_client = compute_v1.FirewallsClient() + operation = firewall_client.delete(project=project_id, firewall=firewall_rule_name) + + wait_for_extended_operation(operation, "firewall rule deletion") + return + + +# [END compute_firewall_delete] diff --git a/compute/client_library/snippets/firewall/list.py b/compute/client_library/snippets/firewall/list.py new file mode 100644 index 00000000000..7a0636ae89c --- /dev/null +++ b/compute/client_library/snippets/firewall/list.py @@ -0,0 +1,48 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_firewall_list] +from typing import Iterable + +from google.cloud import compute_v1 + + +def list_firewall_rules(project_id: str) -> Iterable[compute_v1.Firewall]: + """ + Return a list of all the firewall rules in specified project. Also prints the + list of firewall names and their descriptions. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + + Returns: + A flat list of all firewall rules defined for given project. + """ + firewall_client = compute_v1.FirewallsClient() + firewalls_list = firewall_client.list(project=project_id) + + for firewall in firewalls_list: + print(f" - {firewall.name}: {firewall.description}") + + return firewalls_list + + +# [END compute_firewall_list] diff --git a/compute/client_library/snippets/firewall/main.py b/compute/client_library/snippets/firewall/main.py new file mode 100644 index 00000000000..b506328df3b --- /dev/null +++ b/compute/client_library/snippets/firewall/main.py @@ -0,0 +1,181 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +from typing import Iterable + +from google.cloud import compute_v1 + + +def create_firewall_rule( + project_id: str, firewall_rule_name: str, network: str = "global/networks/default" +) -> compute_v1.Firewall: + """ + Creates a simple firewall rule allowing for incoming HTTP and HTTPS access from the entire Internet. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + firewall_rule_name: name of the rule that is created. + network: name of the network the rule will be applied to. Available name formats: + * https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network} + * projects/{project_id}/global/networks/{network} + * global/networks/{network} + + Returns: + A Firewall object. + """ + firewall_rule = compute_v1.Firewall() + firewall_rule.name = firewall_rule_name + firewall_rule.direction = "INGRESS" + + allowed_ports = compute_v1.Allowed() + allowed_ports.I_p_protocol = "tcp" + allowed_ports.ports = ["80", "443"] + + firewall_rule.allowed = [allowed_ports] + firewall_rule.source_ranges = ["0.0.0.0/0"] + firewall_rule.network = network + firewall_rule.description = "Allowing TCP traffic on port 80 and 443 from Internet." + + firewall_rule.target_tags = ["web"] + + # Note that the default value of priority for the firewall API is 1000. + # If you check the value of `firewall_rule.priority` at this point it + # will be equal to 0, however it is not treated as "set" by the library and thus + # the default will be applied to the new rule. If you want to create a rule that + # has priority == 0, you need to explicitly set it so: + # TODO: Uncomment to set the priority to 0 + # firewall_rule.priority = 0 + + firewall_client = compute_v1.FirewallsClient() + operation = firewall_client.insert( + project=project_id, firewall_resource=firewall_rule + ) + + wait_for_extended_operation(operation, "firewall rule creation") + + return firewall_client.get(project=project_id, firewall=firewall_rule_name) + + +def delete_firewall_rule(project_id: str, firewall_rule_name: str) -> None: + """ + Deletes a firewall rule from the project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + firewall_rule_name: name of the firewall rule you want to delete. + """ + firewall_client = compute_v1.FirewallsClient() + operation = firewall_client.delete(project=project_id, firewall=firewall_rule_name) + + wait_for_extended_operation(operation, "firewall rule deletion") + return + + +def get_firewall_rule(project_id: str, firewall_rule_name: str) -> compute_v1.Firewall: + """ + Retrieve a Firewall from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + firewall_rule_name: name of the firewall rule you want to retrieve. + + Returns: + A Firewall object. + """ + firewall_client = compute_v1.FirewallsClient() + return firewall_client.get(project=project_id, firewall=firewall_rule_name) + + +def list_firewall_rules(project_id: str) -> Iterable[compute_v1.Firewall]: + """ + Return a list of all the firewall rules in specified project. Also prints the + list of firewall names and their descriptions. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + + Returns: + A flat list of all firewall rules defined for given project. + """ + firewall_client = compute_v1.FirewallsClient() + firewalls_list = firewall_client.list(project=project_id) + + for firewall in firewalls_list: + print(f" - {firewall.name}: {firewall.description}") + + return firewalls_list + + +def patch_firewall_priority( + project_id: str, firewall_rule_name: str, priority: int +) -> None: + """ + Modifies the priority of a given firewall rule. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + firewall_rule_name: name of the rule you want to modify. + priority: the new priority to be set for the rule. + """ + firewall_rule = compute_v1.Firewall() + firewall_rule.priority = priority + + # The patch operation doesn't require the full definition of a Firewall object. It will only update + # the values that were set in it, in this case it will only change the priority. + firewall_client = compute_v1.FirewallsClient() + operation = firewall_client.patch( + project=project_id, firewall=firewall_rule_name, firewall_resource=firewall_rule + ) + + wait_for_extended_operation(operation, "firewall rule patching") + return + + +if __name__ == "__main__": + import google.auth + import google.auth.exceptions + + try: + default_project_id = google.auth.default()[1] + print(f"Using project {default_project_id}.") + except google.auth.exceptions.DefaultCredentialsError: + print( + "Please use `gcloud auth application-default login` " + "or set GOOGLE_APPLICATION_CREDENTIALS to use this script." + ) + else: + import uuid + + rule_name = "firewall-sample-" + uuid.uuid4().hex[:10] + print(f"Creating firewall rule {rule_name}...") + # The rule will be created with default priority of 1000. + create_firewall_rule(default_project_id, rule_name) + try: + print("Rule created:") + print(get_firewall_rule(default_project_id, rule_name)) + print("Updating rule priority to 10...") + patch_firewall_priority(default_project_id, rule_name, 10) + print("Rule updated: ") + print(get_firewall_rule(default_project_id, rule_name)) + print(f"Deleting rule {rule_name}...") + finally: + delete_firewall_rule(default_project_id, rule_name) + print("Done.") diff --git a/compute/client_library/snippets/firewall/patch.py b/compute/client_library/snippets/firewall/patch.py new file mode 100644 index 00000000000..f288c35ade0 --- /dev/null +++ b/compute/client_library/snippets/firewall/patch.py @@ -0,0 +1,102 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_firewall_patch] +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: + """ + This method will wait 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 patch_firewall_priority( + project_id: str, firewall_rule_name: str, priority: int +) -> None: + """ + Modifies the priority of a given firewall rule. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + firewall_rule_name: name of the rule you want to modify. + priority: the new priority to be set for the rule. + """ + firewall_rule = compute_v1.Firewall() + firewall_rule.priority = priority + + # The patch operation doesn't require the full definition of a Firewall object. It will only update + # the values that were set in it, in this case it will only change the priority. + firewall_client = compute_v1.FirewallsClient() + operation = firewall_client.patch( + project=project_id, firewall=firewall_rule_name, firewall_resource=firewall_rule + ) + + wait_for_extended_operation(operation, "firewall rule patching") + return + + +# [END compute_firewall_patch] diff --git a/compute/client_library/snippets/firewall/windows_kms.py b/compute/client_library/snippets/firewall/windows_kms.py new file mode 100644 index 00000000000..ed869386fa2 --- /dev/null +++ b/compute/client_library/snippets/firewall/windows_kms.py @@ -0,0 +1,118 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_create_egress_rule_windows_activation] +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: + """ + This method will wait 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_firewall_rule_for_windows_activation_host( + project_id: str, firewall_rule_name: str, network: str = "global/networks/default" +) -> compute_v1.Firewall: + """ + Creates an egress firewall rule with the highest priority for host + kms.windows.googlecloud.com (35.190.247.13) for Windows activation. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + firewall_rule_name: name of the rule that is created. + network: name of the network the rule will be applied to. Available name formats: + * https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network} + * projects/{project_id}/global/networks/{network} + * global/networks/{network} + + Returns: + A Firewall object. + """ + firewall_rule = compute_v1.Firewall() + firewall_rule.name = firewall_rule_name + firewall_rule.network = network + + allowed = compute_v1.Allowed() + allowed.ports = ["1688"] + allowed.I_p_protocol = "tcp" + + firewall_rule.allowed = [allowed] + firewall_rule.destination_ranges = ["35.190.247.13/32"] + firewall_rule.direction = compute_v1.Firewall.Direction.EGRESS.name + firewall_rule.priority = 0 + + firewall_client = compute_v1.FirewallsClient() + operation = firewall_client.insert( + project=project_id, firewall_resource=firewall_rule + ) + + wait_for_extended_operation(operation, "windows KSM firewall rule creation") + + return firewall_client.get(project=project_id, firewall=firewall_rule_name) + + +# [END compute_create_egress_rule_windows_activation] diff --git a/compute/client_library/snippets/images/__init__.py b/compute/client_library/snippets/images/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/snippets/images/create.py b/compute/client_library/snippets/images/create.py new file mode 100644 index 00000000000..21fa76dd3fa --- /dev/null +++ b/compute/client_library/snippets/images/create.py @@ -0,0 +1,154 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_windows_image_create] +# [START compute_images_create] +import sys +from typing import Any, Optional +import warnings + +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: + """ + This method will wait 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 + + +STOPPED_MACHINE_STATUS = ( + compute_v1.Instance.Status.TERMINATED.name, + compute_v1.Instance.Status.STOPPED.name, +) + + +def create_image_from_disk( + project_id: str, + zone: str, + source_disk_name: str, + image_name: str, + storage_location: Optional[str] = None, + force_create: bool = False, +) -> compute_v1.Image: + """ + Creates a new disk image. + + Args: + project_id: project ID or project number of the Cloud project you use. + zone: zone of the disk you copy from. + source_disk_name: name of the source disk you copy from. + image_name: name of the image you want to create. + storage_location: storage location for the image. If the value is undefined, + function will store the image in the multi-region closest to your image's + source location. + force_create: create the image even if the source disk is attached to a + running instance. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + disk_client = compute_v1.DisksClient() + instance_client = compute_v1.InstancesClient() + + # Get source disk + disk = disk_client.get(project=project_id, zone=zone, disk=source_disk_name) + + for disk_user in disk.users: + instance = instance_client.get( + project=project_id, zone=zone, instance=disk_user + ) + if instance.status in STOPPED_MACHINE_STATUS: + continue + if not force_create: + raise RuntimeError( + f"Instance {disk_user} should be stopped. For Windows instances please " + f"stop the instance using `GCESysprep` command. For Linux instances just " + f"shut it down normally. You can supress this error and create an image of" + f"the disk by setting `force_create` parameter to true (not recommended). \n" + f"More information here: \n" + f" * https://cloud.google.com/compute/docs/instances/windows/creating-windows-os-image#api \n" + f" * https://cloud.google.com/compute/docs/images/create-delete-deprecate-private-images#prepare_instance_for_image" + ) + else: + warnings.warn( + f"Warning: The `force_create` option may compromise the integrity of your image. " + f"Stop the {disk_user} instance before you create the image if possible." + ) + + # Create image + image = compute_v1.Image() + image.source_disk = disk.self_link + image.name = image_name + if storage_location: + image.storage_locations = [storage_location] + + operation = image_client.insert(project=project_id, image_resource=image) + + wait_for_extended_operation(operation, "image creation from disk") + + return image_client.get(project=project_id, image=image_name) + + +# [END compute_images_create] +# [END compute_windows_image_create] diff --git a/compute/client_library/snippets/images/create_from_image.py b/compute/client_library/snippets/images/create_from_image.py new file mode 100644 index 00000000000..3dddea1a599 --- /dev/null +++ b/compute/client_library/snippets/images/create_from_image.py @@ -0,0 +1,128 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_images_create_from_image] +import sys +from typing import Any, Iterable, Optional + +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: + """ + This method will wait 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_image_from_image( + project_id: str, + source_image_name: str, + image_name: str, + source_project_id: Optional[str] = None, + guest_os_features: Optional[Iterable[str]] = None, + storage_location: Optional[str] = None, +) -> compute_v1.Image: + """ + Creates a copy of another image. + + Args: + project_id: project ID or project number of the Cloud project you want to place your new image in. + source_image_name: name of the image you want to copy. + image_name: name of the image you want to create. + source_project_id: name of the project that hosts the source image. If left unset, it's assumed to equal + the `project_id`. + guest_os_features: an iterable collection of guest features you want to enable for the bootable image. + Learn more about Guest OS features here: + https://cloud.google.com/compute/docs/images/create-delete-deprecate-private-images#guest-os-features + storage_location: the storage location of your image. For example, specify "us" to store the image in the + `us` multi-region, or "us-central1" to store it in the `us-central1` region. If you do not make a selection, + Compute Engine stores the image in the multi-region closest to your image's source location. + + Returns: + An Image object. + """ + if source_project_id is None: + source_project_id = project_id + + image_client = compute_v1.ImagesClient() + src_image = image_client.get(project=source_project_id, image=source_image_name) + + image = compute_v1.Image() + image.name = image_name + image.source_image = src_image.self_link + if storage_location: + image.storage_locations = [storage_location] + + if guest_os_features: + image.guest_os_features = [ + compute_v1.GuestOsFeature(type_=feature) for feature in guest_os_features + ] + + operation = image_client.insert(project=project_id, image_resource=image) + + wait_for_extended_operation(operation, "image creation from image") + + return image_client.get(project=project_id, image=image_name) + + +# [END compute_images_create_from_image] diff --git a/compute/client_library/snippets/images/create_from_snapshot.py b/compute/client_library/snippets/images/create_from_snapshot.py new file mode 100644 index 00000000000..18365ea0632 --- /dev/null +++ b/compute/client_library/snippets/images/create_from_snapshot.py @@ -0,0 +1,132 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_images_create_from_snapshot] +import sys +from typing import Any, Iterable, Optional + +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: + """ + This method will wait 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_image_from_snapshot( + project_id: str, + source_snapshot_name: str, + image_name: str, + source_project_id: Optional[str] = None, + guest_os_features: Optional[Iterable[str]] = None, + storage_location: Optional[str] = None, +) -> compute_v1.Image: + """ + Creates an image based on a snapshot. + + Args: + project_id: project ID or project number of the Cloud project you want to place your new image in. + source_snapshot_name: name of the snapshot you want to use as a base of your image. + image_name: name of the image you want to create. + source_project_id: name of the project that hosts the source snapshot. If left unset, it's assumed to equal + the `project_id`. + guest_os_features: an iterable collection of guest features you want to enable for the bootable image. + Learn more about Guest OS features here: + https://cloud.google.com/compute/docs/images/create-delete-deprecate-private-images#guest-os-features + storage_location: the storage location of your image. For example, specify "us" to store the image in the + `us` multi-region, or "us-central1" to store it in the `us-central1` region. If you do not make a selection, + Compute Engine stores the image in the multi-region closest to your image's source location. + + Returns: + An Image object. + """ + if source_project_id is None: + source_project_id = project_id + + snapshot_client = compute_v1.SnapshotsClient() + image_client = compute_v1.ImagesClient() + src_snapshot = snapshot_client.get( + project=source_project_id, snapshot=source_snapshot_name + ) + + image = compute_v1.Image() + image.name = image_name + image.source_snapshot = src_snapshot.self_link + + if storage_location: + image.storage_locations = [storage_location] + + if guest_os_features: + image.guest_os_features = [ + compute_v1.GuestOsFeature(type_=feature) for feature in guest_os_features + ] + + operation = image_client.insert(project=project_id, image_resource=image) + + wait_for_extended_operation(operation, "image creation from snapshot") + + return image_client.get(project=project_id, image=image_name) + + +# [END compute_images_create_from_snapshot] diff --git a/compute/client_library/snippets/images/delete.py b/compute/client_library/snippets/images/delete.py new file mode 100644 index 00000000000..5412bcd686a --- /dev/null +++ b/compute/client_library/snippets/images/delete.py @@ -0,0 +1,90 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_images_delete] +import sys +from typing import Any, NoReturn + +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: + """ + This method will wait 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 delete_image(project_id: str, image_name: str) -> NoReturn: + """ + Deletes a disk image. + + Args: + project_id: project ID or project number of the Cloud project you use. + image_name: name of the image you want to delete. + """ + image_client = compute_v1.ImagesClient() + operation = image_client.delete(project=project_id, image=image_name) + wait_for_extended_operation(operation, "image deletion") + + +# [END compute_images_delete] diff --git a/compute/client_library/snippets/images/get.py b/compute/client_library/snippets/images/get.py new file mode 100644 index 00000000000..dbf8b3a2625 --- /dev/null +++ b/compute/client_library/snippets/images/get.py @@ -0,0 +1,65 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_images_get_from_family] +# [START compute_images_get] +from google.cloud import compute_v1 + +# [END compute_images_get] + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +# [END compute_images_get_from_family] + + +# [START compute_images_get] +def get_image(project_id: str, image_name: str) -> compute_v1.Image: + """ + Retrieve detailed information about a single image from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to list images from. + image_name: name of the image you want to get details of. + + Returns: + An instance of compute_v1.Image object with information about specified image. + """ + image_client = compute_v1.ImagesClient() + return image_client.get(project=project_id, image=image_name) + + +# [END compute_images_get] diff --git a/compute/client_library/snippets/images/list.py b/compute/client_library/snippets/images/list.py new file mode 100644 index 00000000000..3618295331f --- /dev/null +++ b/compute/client_library/snippets/images/list.py @@ -0,0 +1,42 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_images_get_list] +from typing import Iterable + +from google.cloud import compute_v1 + + +def list_images(project_id: str) -> Iterable[compute_v1.Image]: + """ + Retrieve a list of images available in given project. + + Args: + project_id: project ID or project number of the Cloud project you want to list images from. + + Returns: + An iterable collection of compute_v1.Image objects. + """ + image_client = compute_v1.ImagesClient() + return image_client.list(project=project_id) + + +# [END compute_images_get_list] diff --git a/compute/client_library/snippets/images/pagination.py b/compute/client_library/snippets/images/pagination.py new file mode 100644 index 00000000000..603480b15c0 --- /dev/null +++ b/compute/client_library/snippets/images/pagination.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_images_list_page] +# [START compute_images_list] +import google.cloud.compute_v1 as compute_v1 + +# [END compute_images_list] +# [END compute_images_list_page] + + +# [START compute_images_list] +def print_images_list(project: str) -> str: + """ + Prints a list of all non-deprecated image names available in given project. + + Args: + project: project ID or project number of the Cloud project you want to list images from. + + Returns: + The output as a string. + """ + images_client = compute_v1.ImagesClient() + # Listing only non-deprecated images to reduce the size of the reply. + images_list_request = compute_v1.ListImagesRequest( + project=project, max_results=100, filter="deprecated.state != DEPRECATED" + ) + output = [] + + # Although the `max_results` parameter is specified in the request, the iterable returned + # by the `list()` method hides the pagination mechanic. The library makes multiple + # requests to the API for you, so you can simply iterate over all the images. + for img in images_client.list(request=images_list_request): + print(f" - {img.name}") + output.append(f" - {img.name}") + return "\n".join(output) + + +# [END compute_images_list] + + +# [START compute_images_list_page] +def print_images_list_by_page(project: str, page_size: int = 10) -> str: + """ + Prints a list of all non-deprecated image names available in a given project, + divided into pages as returned by the Compute Engine API. + + Args: + project: project ID or project number of the Cloud project you want to list images from. + page_size: size of the pages you want the API to return on each call. + + Returns: + Output as a string. + """ + images_client = compute_v1.ImagesClient() + # Listing only non-deprecated images to reduce the size of the reply. + images_list_request = compute_v1.ListImagesRequest( + project=project, max_results=page_size, filter="deprecated.state != DEPRECATED" + ) + output = [] + + # Use the `pages` attribute of returned iterable to have more granular control of + # iteration over paginated results from the API. Each time you want to access the + # next page, the library retrieves that page from the API. + for page_num, page in enumerate( + images_client.list(request=images_list_request).pages, start=1 + ): + print(f"Page {page_num}: ") + output.append(f"Page {page_num}: ") + for img in page.items: + print(f" - {img.name}") + output.append(f" - {img.name}") + return "\n".join(output) + + +# [END compute_images_list_page] + + +if __name__ == "__main__": + print("=================== Flat list of images ===================") + print_images_list("windows-sql-cloud") + print("================= Paginated list of images ================") + print_images_list_by_page("windows-sql-cloud", 5) diff --git a/compute/client_library/snippets/images/set_deprecation_status.py b/compute/client_library/snippets/images/set_deprecation_status.py new file mode 100644 index 00000000000..c98ee04adc3 --- /dev/null +++ b/compute/client_library/snippets/images/set_deprecation_status.py @@ -0,0 +1,104 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_images_set_deprecation_status] +import sys +from typing import Any, NoReturn + +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: + """ + This method will wait 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 set_deprecation_status( + project_id: str, image_name: str, status: compute_v1.DeprecationStatus.State +) -> NoReturn: + """ + Modify the deprecation status of an image. + + Note: Image objects by default don't have the `deprecated` attribute at all unless it's set. + + Args: + project_id: project ID or project number of the Cloud project that hosts the image. + image_name: name of the image you want to modify + status: the status you want to set for the image. Available values are available in + `compute_v1.DeprecationStatus.State` enum. Learn more about image deprecation statuses: + https://cloud.google.com/compute/docs/images/create-delete-deprecate-private-images#deprecation-states + """ + image_client = compute_v1.ImagesClient() + deprecation_status = compute_v1.DeprecationStatus() + deprecation_status.state = status.name + operation = image_client.deprecate( + project=project_id, + image=image_name, + deprecation_status_resource=deprecation_status, + ) + + wait_for_extended_operation(operation, "changing deprecation state of an image") + + +# [END compute_images_set_deprecation_status] diff --git a/compute/client_library/snippets/instance_templates/__init__.py b/compute/client_library/snippets/instance_templates/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/snippets/instance_templates/create.py b/compute/client_library/snippets/instance_templates/create.py new file mode 100644 index 00000000000..1041206c914 --- /dev/null +++ b/compute/client_library/snippets/instance_templates/create.py @@ -0,0 +1,129 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_template_create] +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: + """ + This method will wait 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_template(project_id: str, template_name: str) -> compute_v1.InstanceTemplate: + """ + Create a new instance template with the provided name and a specific + instance configuration. + + Args: + project_id: project ID or project number of the Cloud project you use. + template_name: name of the new template to create. + + Returns: + InstanceTemplate object that represents the new instance template. + """ + # The template describes the size and source image of the boot disk + # to attach to the instance. + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = ( + "projects/debian-cloud/global/images/family/debian-11" + ) + initialize_params.disk_size_gb = 250 + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + + # The template connects the instance to the `default` network, + # without specifying a subnetwork. + network_interface = compute_v1.NetworkInterface() + network_interface.name = "global/networks/default" + + # The template lets the instance use an external IP address. + access_config = compute_v1.AccessConfig() + access_config.name = "External NAT" + access_config.type_ = "ONE_TO_ONE_NAT" + access_config.network_tier = "PREMIUM" + network_interface.access_configs = [access_config] + + template = compute_v1.InstanceTemplate() + template.name = template_name + template.properties.disks = [disk] + template.properties.machine_type = "e2-standard-4" + template.properties.network_interfaces = [network_interface] + + template_client = compute_v1.InstanceTemplatesClient() + operation = template_client.insert( + project=project_id, instance_template_resource=template + ) + + wait_for_extended_operation(operation, "instance template creation") + + return template_client.get(project=project_id, instance_template=template_name) + + +# [END compute_template_create] diff --git a/compute/client_library/snippets/instance_templates/create_from_instance.py b/compute/client_library/snippets/instance_templates/create_from_instance.py new file mode 100644 index 00000000000..a77c813eefb --- /dev/null +++ b/compute/client_library/snippets/instance_templates/create_from_instance.py @@ -0,0 +1,119 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_template_create_from_instance] +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: + """ + This method will wait 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_template_from_instance( + project_id: str, instance: str, template_name: str +) -> compute_v1.InstanceTemplate: + """ + Create a new instance template based on an existing instance. + This new template specifies a different boot disk. + + Args: + project_id: project ID or project number of the Cloud project you use. + instance: the instance to base the new template on. This value uses + the following format: "projects/{project}/zones/{zone}/instances/{instance_name}" + template_name: name of the new template to create. + + Returns: + InstanceTemplate object that represents the new instance template. + """ + disk = compute_v1.DiskInstantiationConfig() + # Device name must match the name of a disk attached to the instance you are + # basing your template on. + disk.device_name = "disk-1" + # Replace the original boot disk image used in your instance with a Rocky Linux image. + disk.instantiate_from = "CUSTOM_IMAGE" + disk.custom_image = "projects/rocky-linux-cloud/global/images/family/rocky-linux-8" + # Override the auto_delete setting. + disk.auto_delete = True + + template = compute_v1.InstanceTemplate() + template.name = template_name + template.source_instance = instance + template.source_instance_params = compute_v1.SourceInstanceParams() + template.source_instance_params.disk_configs = [disk] + + template_client = compute_v1.InstanceTemplatesClient() + operation = template_client.insert( + project=project_id, instance_template_resource=template + ) + + wait_for_extended_operation(operation, "instance template creation") + + return template_client.get(project=project_id, instance_template=template_name) + + +# [END compute_template_create_from_instance] diff --git a/compute/client_library/snippets/instance_templates/create_with_subnet.py b/compute/client_library/snippets/instance_templates/create_with_subnet.py new file mode 100644 index 00000000000..3051e949534 --- /dev/null +++ b/compute/client_library/snippets/instance_templates/create_with_subnet.py @@ -0,0 +1,127 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_template_create_with_subnet] +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: + """ + This method will wait 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_template_with_subnet( + project_id: str, network: str, subnetwork: str, template_name: str +) -> compute_v1.InstanceTemplate: + """ + Create an instance template that uses a provided subnet. + + Args: + project_id: project ID or project number of the Cloud project you use. + network: the network to be used in the new template. This value uses + the following format: "projects/{project}/global/networks/{network}" + subnetwork: the subnetwork to be used in the new template. This value + uses the following format: "projects/{project}/regions/{region}/subnetworks/{subnetwork}" + template_name: name of the new template to create. + + Returns: + InstanceTemplate object that represents the new instance template. + """ + # The template describes the size and source image of the book disk to + # attach to the instance. + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = ( + "projects/debian-cloud/global/images/family/debian-11" + ) + initialize_params.disk_size_gb = 250 + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + + template = compute_v1.InstanceTemplate() + template.name = template_name + template.properties = compute_v1.InstanceProperties() + template.properties.disks = [disk] + template.properties.machine_type = "e2-standard-4" + + # The template connects the instance to the specified network and subnetwork. + network_interface = compute_v1.NetworkInterface() + network_interface.network = network + network_interface.subnetwork = subnetwork + template.properties.network_interfaces = [network_interface] + + template_client = compute_v1.InstanceTemplatesClient() + operation = template_client.insert( + project=project_id, instance_template_resource=template + ) + wait_for_extended_operation(operation, "instance template creation") + + return template_client.get(project=project_id, instance_template=template_name) + + +# [END compute_template_create_with_subnet] diff --git a/compute/client_library/snippets/instance_templates/delete.py b/compute/client_library/snippets/instance_templates/delete.py new file mode 100644 index 00000000000..382fd64d4c8 --- /dev/null +++ b/compute/client_library/snippets/instance_templates/delete.py @@ -0,0 +1,93 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_template_delete] +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: + """ + This method will wait 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 delete_instance_template(project_id: str, template_name: str): + """ + Delete an instance template. + + Args: + project_id: project ID or project number of the Cloud project you use. + template_name: name of the template to delete. + """ + template_client = compute_v1.InstanceTemplatesClient() + operation = template_client.delete( + project=project_id, instance_template=template_name + ) + wait_for_extended_operation(operation, "instance template deletion") + return + + +# [END compute_template_delete] diff --git a/compute/client_library/snippets/instance_templates/get.py b/compute/client_library/snippets/instance_templates/get.py new file mode 100644 index 00000000000..439b3bea979 --- /dev/null +++ b/compute/client_library/snippets/instance_templates/get.py @@ -0,0 +1,44 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_template_get] +from google.cloud import compute_v1 + + +def get_instance_template( + project_id: str, template_name: str +) -> compute_v1.InstanceTemplate: + """ + Retrieve an instance template, which you can use to create virtual machine + (VM) instances and managed instance groups (MIGs). + + Args: + project_id: project ID or project number of the Cloud project you use. + template_name: name of the template to retrieve. + + Returns: + InstanceTemplate object that represents the retrieved template. + """ + template_client = compute_v1.InstanceTemplatesClient() + return template_client.get(project=project_id, instance_template=template_name) + + +# [END compute_template_get] diff --git a/compute/client_library/snippets/instance_templates/list.py b/compute/client_library/snippets/instance_templates/list.py new file mode 100644 index 00000000000..495686c62d8 --- /dev/null +++ b/compute/client_library/snippets/instance_templates/list.py @@ -0,0 +1,42 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_template_list] +from typing import Iterable + +from google.cloud import compute_v1 + + +def list_instance_templates(project_id: str) -> Iterable[compute_v1.InstanceTemplate]: + """ + Get a list of InstanceTemplate objects available in a project. + + Args: + project_id: project ID or project number of the Cloud project you use. + + Returns: + Iterable list of InstanceTemplate objects. + """ + template_client = compute_v1.InstanceTemplatesClient() + return template_client.list(project=project_id) + + +# [END compute_template_list] diff --git a/compute/client_library/snippets/instances/__init__.py b/compute/client_library/snippets/instances/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/snippets/instances/bulk_insert.py b/compute/client_library/snippets/instances/bulk_insert.py new file mode 100644 index 00000000000..efe095e3e1f --- /dev/null +++ b/compute/client_library/snippets/instances/bulk_insert.py @@ -0,0 +1,191 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_bulk_insert] +import sys +from typing import Any, Iterable, Optional +import uuid + +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: + """ + This method will wait 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 get_instance_template( + project_id: str, template_name: str +) -> compute_v1.InstanceTemplate: + """ + Retrieve an instance template, which you can use to create virtual machine + (VM) instances and managed instance groups (MIGs). + + Args: + project_id: project ID or project number of the Cloud project you use. + template_name: name of the template to retrieve. + + Returns: + InstanceTemplate object that represents the retrieved template. + """ + template_client = compute_v1.InstanceTemplatesClient() + return template_client.get(project=project_id, instance_template=template_name) + + +def bulk_insert_instance( + project_id: str, + zone: str, + template: compute_v1.InstanceTemplate, + count: int, + name_pattern: str, + min_count: Optional[int] = None, + labels: Optional[dict] = None, +) -> Iterable[compute_v1.Instance]: + """ + Create multiple VMs based on an Instance Template. The newly created instances will + be returned as a list and will share a label with key `bulk_batch` and a random + value. + + If the bulk insert operation fails and the requested number of instances can't be created, + and more than min_count instances are created, then those instances can be found using + the `bulk_batch` label with value attached to the raised exception in bulk_batch_id + attribute. So, you can use the following filter: f"label.bulk_batch={err.bulk_batch_id}" + when listing instances in a zone to get the instances that were successfully created. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + template: an Instance Template to be used for creation of the new VMs. + name_pattern: The string pattern used for the names of the VMs. The pattern + must contain one continuous sequence of placeholder hash characters (#) + with each character corresponding to one digit of the generated instance + name. Example: a name_pattern of inst-#### generates instance names such + as inst-0001 and inst-0002. If existing instances in the same project and + zone have names that match the name pattern then the generated instance + numbers start after the biggest existing number. For example, if there + exists an instance with name inst-0050, then instance names generated + using the pattern inst-#### begin with inst-0051. The name pattern + placeholder #...# can contain up to 18 characters. + count: The maximum number of instances to create. + min_count (optional): The minimum number of instances to create. If no min_count is + specified then count is used as the default value. If min_count instances + cannot be created, then no instances will be created and instances already + created will be deleted. + labels (optional): A dictionary with labels to be added to the new VMs. + """ + bulk_insert_resource = compute_v1.BulkInsertInstanceResource() + bulk_insert_resource.source_instance_template = template.self_link + bulk_insert_resource.count = count + bulk_insert_resource.min_count = min_count or count + bulk_insert_resource.name_pattern = name_pattern + + if not labels: + labels = {} + + labels["bulk_batch"] = uuid.uuid4().hex + instance_prop = compute_v1.InstanceProperties() + instance_prop.labels = labels + bulk_insert_resource.instance_properties = instance_prop + + bulk_insert_request = compute_v1.BulkInsertInstanceRequest() + bulk_insert_request.bulk_insert_instance_resource_resource = bulk_insert_resource + bulk_insert_request.project = project_id + bulk_insert_request.zone = zone + + client = compute_v1.InstancesClient() + operation = client.bulk_insert(bulk_insert_request) + + try: + wait_for_extended_operation(operation, "bulk instance creation") + except Exception as err: + err.bulk_batch_id = labels["bulk_batch"] + raise err + + list_req = compute_v1.ListInstancesRequest() + list_req.project = project_id + list_req.zone = zone + list_req.filter = " AND ".join( + f"labels.{key}:{value}" for key, value in labels.items() + ) + return client.list(list_req) + + +def create_five_instances( + project_id: str, zone: str, template_name: str, name_pattern: str +): + """ + Create five instances of an instance template. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + template_name: name of the template that will be used to create new VMs. + name_pattern: The string pattern used for the names of the VMs. + """ + template = get_instance_template(project_id, template_name) + instances = bulk_insert_instance(project_id, zone, template, 5, name_pattern) + return instances + + +# [END compute_instances_bulk_insert] diff --git a/compute/client_library/snippets/instances/create.py b/compute/client_library/snippets/instances/create.py new file mode 100644 index 00000000000..801c05b16a8 --- /dev/null +++ b/compute/client_library/snippets/instances/create.py @@ -0,0 +1,290 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_create] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +# [END compute_instances_create] + +if __name__ == "__main__": + import uuid + import google.auth + import google.auth.exceptions + + try: + default_project_id = google.auth.default()[1] + except google.auth.exceptions.DefaultCredentialsError: + print( + "Please use `gcloud auth application-default login` " + "or set GOOGLE_APPLICATION_CREDENTIALS to use this script." + ) + else: + instance_name = "quickstart-" + uuid.uuid4().hex[:10] + instance_zone = "europe-central2-b" + + newest_debian = get_image_from_family( + project="debian-cloud", family="debian-10" + ) + disk_type = f"zones/{instance_zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + + create_instance(default_project_id, instance_zone, instance_name, disks) diff --git a/compute/client_library/snippets/instances/create_start_instance/__init__.py b/compute/client_library/snippets/instances/create_start_instance/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/snippets/instances/create_start_instance/create_from_custom_image.py b/compute/client_library/snippets/instances/create_start_instance/create_from_custom_image.py new file mode 100644 index 00000000000..d78dd819868 --- /dev/null +++ b/compute/client_library/snippets/instances/create_start_instance/create_from_custom_image.py @@ -0,0 +1,288 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_create_from_custom_image] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_from_custom_image( + project_id: str, zone: str, instance_name: str, custom_image_link: str +) -> compute_v1.Instance: + """ + Create a new VM instance with custom image used as its boot disk. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + custom_image_link: link to the custom image you want to use in the form of: + "projects/{project_name}/global/images/{image_name}" + + Returns: + Instance object. + """ + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, custom_image_link, True)] + instance = create_instance(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_custom_image] diff --git a/compute/client_library/snippets/instances/create_start_instance/create_from_public_image.py b/compute/client_library/snippets/instances/create_start_instance/create_from_public_image.py new file mode 100644 index 00000000000..50bf6bffdc9 --- /dev/null +++ b/compute/client_library/snippets/instances/create_start_instance/create_from_public_image.py @@ -0,0 +1,287 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_create_from_image] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_from_public_image( + project_id: str, zone: str, instance_name: str +) -> compute_v1.Instance: + """ + Create a new VM instance with Debian 10 operating system. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family(project="debian-cloud", family="debian-10") + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link, True)] + instance = create_instance(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_image] diff --git a/compute/client_library/snippets/instances/create_start_instance/create_from_snapshot.py b/compute/client_library/snippets/instances/create_start_instance/create_from_snapshot.py new file mode 100644 index 00000000000..d71bd4500f9 --- /dev/null +++ b/compute/client_library/snippets/instances/create_start_instance/create_from_snapshot.py @@ -0,0 +1,271 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_create_from_snapshot] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def disk_from_snapshot( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_snapshot: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk(): + """ + Create an AttachedDisk object to be used in VM instance creation. Uses a disk snapshot as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_snapshot: disk snapshot to use when creating this disk. You must have read access to this disk. + This value uses the following format: "projects/{project_name}/global/snapshots/{snapshot_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified snapshot. + """ + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_snapshot = source_snapshot + initialize_params.disk_type = disk_type + initialize_params.disk_size_gb = disk_size_gb + disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + disk.auto_delete = auto_delete + disk.boot = boot + return disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_from_snapshot( + project_id: str, zone: str, instance_name: str, snapshot_link: str +): + """ + Create a new VM instance with boot disk created from a snapshot. The + new boot disk will have 20 gigabytes. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + snapshot_link: link to the snapshot you want to use as the source of your + boot disk in the form of: "projects/{project_name}/global/snapshots/{snapshot_name}" + + Returns: + Instance object. + """ + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_snapshot(disk_type, 20, True, snapshot_link)] + instance = create_instance(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_snapshot] diff --git a/compute/client_library/snippets/instances/create_start_instance/create_windows_instance.py b/compute/client_library/snippets/instances/create_start_instance/create_windows_instance.py new file mode 100644 index 00000000000..ce0620da216 --- /dev/null +++ b/compute/client_library/snippets/instances/create_start_instance/create_windows_instance.py @@ -0,0 +1,331 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_create_windows_instance_external_ip] +# [START compute_create_windows_instance_internal_ip] +import re +import sys +from typing import Any, List, Optional +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_windows_instance( + project_id: str, + zone: str, + instance_name: str, + machine_type: str, + source_image_family: str = "windows-2022", + network_link: str = "global/networks/default", + subnetwork_link: Optional[str] = None, +) -> compute_v1.Instance: + """ + Creates a new Windows Server instance that has only an internal IP address. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + machine_type: machine type you want to create in following format: + "zones/{zone}/machineTypes/{type_name}". For example: + "zones/europe-west3-c/machineTypes/f1-micro" + You can find the list of available machine types using: + https://cloud.google.com/sdk/gcloud/reference/compute/machine-types/list + source_image_family: name of the public image family for Windows Server or SQL Server images. + https://cloud.google.com/compute/docs/images#os-compute-support + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + + Returns: + Instance object. + """ + if subnetwork_link is None: + subnetwork_link = f"regions/{zone}/subnetworks/default" + + base_image = get_image_from_family( + project="windows-cloud", family=source_image_family + ) + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 100, True, base_image.self_link, True)] + + # You must verify or configure routes and firewall rules in your VPC network + # to allow access to kms.windows.googlecloud.com. + # More information about access to kms.windows.googlecloud.com: https://cloud.google.com/compute/docs/instances/windows/creating-managing-windows-instances#kms-server + + # Additionally, you must enable Private Google Access for subnets in your VPC network + # that contain Windows instances with only internal IP addresses. + # More information about Private Google Access: https://cloud.google.com/vpc/docs/configure-private-google-access#enabling + + instance = create_instance( + project_id, + zone, + instance_name, + disks, + machine_type=machine_type, + network_link=network_link, + subnetwork_link=subnetwork_link, + external_access=True, # Set this to False to disable external IP for your instance + ) + return instance + + +# [END compute_create_windows_instance_internal_ip] +# [END compute_create_windows_instance_external_ip] diff --git a/compute/client_library/snippets/instances/create_start_instance/create_with_additional_disk.py b/compute/client_library/snippets/instances/create_start_instance/create_with_additional_disk.py new file mode 100644 index 00000000000..e75aadccd78 --- /dev/null +++ b/compute/client_library/snippets/instances/create_start_instance/create_with_additional_disk.py @@ -0,0 +1,321 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_create_from_image_plus_empty_disk] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def empty_disk( + disk_type: str, disk_size_gb: int, boot: bool = False, auto_delete: bool = True +) -> compute_v1.AttachedDisk(): + """ + Create an AttachedDisk object to be used in VM instance creation. The created disk contains + no data and requires formatting before it can be used. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created as an empty disk. + """ + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.disk_type = disk_type + initialize_params.disk_size_gb = disk_size_gb + disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + disk.auto_delete = auto_delete + disk.boot = boot + return disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_with_additional_disk( + project_id: str, zone: str, instance_name: str +) -> compute_v1.Instance: + """ + Create a new VM instance with Debian 10 operating system on a 20 GB disk + and a 25 GB additional empty disk. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family(project="debian-cloud", family="debian-10") + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [ + disk_from_image(disk_type, 20, True, newest_debian.self_link), + empty_disk(disk_type, 25), + ] + instance = create_instance(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_image_plus_empty_disk] diff --git a/compute/client_library/snippets/instances/create_start_instance/create_with_existing_disks.py b/compute/client_library/snippets/instances/create_start_instance/create_with_existing_disks.py new file mode 100644 index 00000000000..4fa4cd2e34c --- /dev/null +++ b/compute/client_library/snippets/instances/create_start_instance/create_with_existing_disks.py @@ -0,0 +1,253 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_create_with_existing_disks] +import re +import sys +from typing import Any, Iterable, List, NoReturn +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_disk(project_id: str, zone: str, disk_name: str) -> compute_v1.Disk: + """ + Gets a disk from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone where the disk exists. + disk_name: name of the disk you want to retrieve. + """ + disk_client = compute_v1.DisksClient() + return disk_client.get(project=project_id, zone=zone, disk=disk_name) + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_with_existing_disks( + project_id: str, zone: str, instance_name: str, disk_names: List[str] +) -> compute_v1.Instance: + """ + Create a new VM instance using selected disks. The first disk in disk_names will + be used as boot disk. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disk_names: list of disk names to be attached to the new virtual machine. + First disk in this list will be used as the boot device. + + Returns: + Instance object. + """ + assert len(disk_names) >= 1 + disks = [get_disk(project_id, zone, disk_name) for disk_name in disk_names] + attached_disks = [] + for disk in disks: + adisk = compute_v1.AttachedDisk() + adisk.source = disk.self_link + attached_disks.append(adisk) + attached_disks[0].boot = True + instance = create_instance(project_id, zone, instance_name, attached_disks) + return instance + + +# [END compute_instances_create_with_existing_disks] diff --git a/compute/client_library/snippets/instances/create_start_instance/create_with_local_ssd.py b/compute/client_library/snippets/instances/create_start_instance/create_with_local_ssd.py new file mode 100644 index 00000000000..c8038abd276 --- /dev/null +++ b/compute/client_library/snippets/instances/create_start_instance/create_with_local_ssd.py @@ -0,0 +1,310 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_create_with_local_ssd] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def local_ssd_disk(zone: str) -> compute_v1.AttachedDisk(): + """ + Create an AttachedDisk object to be used in VM instance creation. The created disk contains + no data and requires formatting before it can be used. + + Args: + zone: The zone in which the local SSD drive will be attached. + + Returns: + AttachedDisk object configured as a local SSD disk. + """ + disk = compute_v1.AttachedDisk() + disk.type_ = compute_v1.AttachedDisk.Type.SCRATCH.name + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.disk_type = f"zones/{zone}/diskTypes/local-ssd" + disk.initialize_params = initialize_params + disk.auto_delete = True + return disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_with_ssd( + project_id: str, zone: str, instance_name: str +) -> compute_v1.Instance: + """ + Create a new VM instance with Debian 10 operating system and SSD local disk. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family(project="debian-cloud", family="debian-10") + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [ + disk_from_image(disk_type, 10, True, newest_debian.self_link, True), + local_ssd_disk(zone), + ] + instance = create_instance(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_with_local_ssd] diff --git a/compute/client_library/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py b/compute/client_library/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py new file mode 100644 index 00000000000..057eba548e6 --- /dev/null +++ b/compute/client_library/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py @@ -0,0 +1,329 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_create_from_image_plus_snapshot_disk] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def disk_from_snapshot( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_snapshot: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk(): + """ + Create an AttachedDisk object to be used in VM instance creation. Uses a disk snapshot as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_snapshot: disk snapshot to use when creating this disk. You must have read access to this disk. + This value uses the following format: "projects/{project_name}/global/snapshots/{snapshot_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified snapshot. + """ + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_snapshot = source_snapshot + initialize_params.disk_type = disk_type + initialize_params.disk_size_gb = disk_size_gb + disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + disk.auto_delete = auto_delete + disk.boot = boot + return disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_with_snapshotted_data_disk( + project_id: str, zone: str, instance_name: str, snapshot_link: str +): + """ + Create a new VM instance with Debian 10 operating system and data disk created from snapshot. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + snapshot_link: link to the snapshot you want to use as the source of your + data disk in the form of: "projects/{project_name}/global/snapshots/{snapshot_name}" + + Returns: + Instance object. + """ + newest_debian = get_image_from_family(project="debian-cloud", family="debian-10") + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [ + disk_from_image(disk_type, 10, True, newest_debian.self_link), + disk_from_snapshot(disk_type, 11, False, snapshot_link), + ] + instance = create_instance(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_image_plus_snapshot_disk] diff --git a/compute/client_library/snippets/instances/create_with_subnet.py b/compute/client_library/snippets/instances/create_with_subnet.py new file mode 100644 index 00000000000..6696dc4a198 --- /dev/null +++ b/compute/client_library/snippets/instances/create_with_subnet.py @@ -0,0 +1,300 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_create_with_subnet] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_with_subnet( + project_id: str, zone: str, instance_name: str, network_link: str, subnet_link: str +) -> compute_v1.Instance: + """ + Create a new VM instance with Debian 10 operating system in specified network and subnetwork. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + + Returns: + Instance object. + """ + newest_debian = get_image_from_family(project="debian-cloud", family="debian-10") + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + instance = create_instance( + project_id, + zone, + instance_name, + disks, + network_link=network_link, + subnetwork_link=subnet_link, + ) + return instance + + +# [END compute_instances_create_with_subnet] diff --git a/compute/client_library/snippets/instances/custom_hostname/create.py b/compute/client_library/snippets/instances/custom_hostname/create.py new file mode 100644 index 00000000000..6813bc4dcd5 --- /dev/null +++ b/compute/client_library/snippets/instances/custom_hostname/create.py @@ -0,0 +1,290 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_create_custom_hostname] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_instance_custom_hostname( + project_id: str, zone: str, instance_name: str, hostname: str +) -> compute_v1.Instance: + """ + Create a new VM instance with Debian 10 operating system and a custom hostname. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + hostname: the hostname you want to use for the new instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family(project="debian-cloud", family="debian-11") + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + instance = create_instance( + project_id, zone, instance_name, disks, custom_hostname=hostname + ) + return instance + + +# [END compute_instances_create_custom_hostname] diff --git a/compute/client_library/snippets/instances/custom_hostname/get.py b/compute/client_library/snippets/instances/custom_hostname/get.py new file mode 100644 index 00000000000..673d5c81066 --- /dev/null +++ b/compute/client_library/snippets/instances/custom_hostname/get.py @@ -0,0 +1,45 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_get_hostname] +from google.cloud import compute_v1 + + +def get_hostname(project_id: str, zone: str, instance_name: str) -> str: + """ + Retrieve the hostname of given instance. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: "us-west3-b" + instance_name: name of the virtual machine to check. + + Returns: + The hostname of an instance. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + return instance.hostname + + +# [END compute_instances_get_hostname] diff --git a/compute/client_library/snippets/instances/custom_machine_types/__init__.py b/compute/client_library/snippets/instances/custom_machine_types/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/snippets/instances/custom_machine_types/create_shared_with_helper.py b/compute/client_library/snippets/instances/custom_machine_types/create_shared_with_helper.py new file mode 100644 index 00000000000..99dea3ef2da --- /dev/null +++ b/compute/client_library/snippets/instances/custom_machine_types/create_shared_with_helper.py @@ -0,0 +1,495 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_custom_machine_type_create_shared_with_helper] +from collections import namedtuple +from enum import Enum +from enum import unique +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def gb_to_mb(value: int) -> int: + return value << 10 + + +class CustomMachineType: + """ + Allows to create custom machine types to be used with the VM instances. + """ + + @unique + class CPUSeries(Enum): + N1 = "custom" + N2 = "n2-custom" + N2D = "n2d-custom" + E2 = "e2-custom" + E2_MICRO = "e2-custom-micro" + E2_SMALL = "e2-custom-small" + E2_MEDIUM = "e2-custom-medium" + + TypeLimits = namedtuple( + "TypeLimits", + [ + "allowed_cores", + "min_mem_per_core", + "max_mem_per_core", + "allow_extra_memory", + "extra_memory_limit", + ], + ) + + # The limits for various CPU types are described on: + # https://cloud.google.com/compute/docs/general-purpose-machines + LIMITS = { + CPUSeries.E2: TypeLimits(frozenset(range(2, 33, 2)), 512, 8192, False, 0), + CPUSeries.E2_MICRO: TypeLimits(frozenset(), 1024, 2048, False, 0), + CPUSeries.E2_SMALL: TypeLimits(frozenset(), 2048, 4096, False, 0), + CPUSeries.E2_MEDIUM: TypeLimits(frozenset(), 4096, 8192, False, 0), + CPUSeries.N2: TypeLimits( + frozenset(range(2, 33, 2)).union(set(range(36, 129, 4))), + 512, + 8192, + True, + gb_to_mb(624), + ), + CPUSeries.N2D: TypeLimits( + frozenset({2, 4, 8, 16, 32, 48, 64, 80, 96}), 512, 8192, True, gb_to_mb(768) + ), + CPUSeries.N1: TypeLimits( + frozenset({1}.union(range(2, 97, 2))), 922, 6656, True, gb_to_mb(624) + ), + } + + def __init__( + self, zone: str, cpu_series: CPUSeries, memory_mb: int, core_count: int = 0 + ): + self.zone = zone + self.cpu_series = cpu_series + self.limits = self.LIMITS[self.cpu_series] + # Shared machine types (e2-small, e2-medium and e2-micro) always have + # 2 vCPUs: https://cloud.google.com/compute/docs/general-purpose-machines#e2_limitations + self.core_count = 2 if self.is_shared() else core_count + self.memory_mb = memory_mb + self._checked = False + self._check_parameters() + self.extra_memory_used = self._check_extra_memory() + + def is_shared(self): + return self.cpu_series in ( + CustomMachineType.CPUSeries.E2_SMALL, + CustomMachineType.CPUSeries.E2_MICRO, + CustomMachineType.CPUSeries.E2_MEDIUM, + ) + + def _check_extra_memory(self) -> bool: + if self._checked: + return self.memory_mb > self.core_count * self.limits.max_mem_per_core + else: + raise RuntimeError( + "You need to call _check_parameters() before calling _check_extra_memory()" + ) + + def _check_parameters(self): + """ + Check whether the requested parameters are allowed. Find more information about limitations of custom machine + types at: https://cloud.google.com/compute/docs/general-purpose-machines#custom_machine_types + """ + # Check the number of cores + if ( + self.limits.allowed_cores + and self.core_count not in self.limits.allowed_cores + ): + raise RuntimeError( + f"Invalid number of cores requested. Allowed number of cores for {self.cpu_series.name} is: {sorted(self.limits.allowed_cores)}" + ) + + # Memory must be a multiple of 256 MB + if self.memory_mb % 256 != 0: + raise RuntimeError("Requested memory must be a multiple of 256 MB.") + + # Check if the requested memory isn't too little + if self.memory_mb < self.core_count * self.limits.min_mem_per_core: + raise RuntimeError( + f"Requested memory is too low. Minimal memory for {self.cpu_series.name} is {self.limits.min_mem_per_core} MB per core." + ) + + # Check if the requested memory isn't too much + if self.memory_mb > self.core_count * self.limits.max_mem_per_core: + if self.limits.allow_extra_memory: + if self.memory_mb > self.limits.extra_memory_limit: + raise RuntimeError( + f"Requested memory is too large.. Maximum memory allowed for {self.cpu_series.name} is {self.limits.extra_memory_limit} MB." + ) + else: + raise RuntimeError( + f"Requested memory is too large.. Maximum memory allowed for {self.cpu_series.name} is {self.limits.max_mem_per_core} MB per core." + ) + + self._checked = True + + def __str__(self) -> str: + """ + Return the custom machine type in form of a string acceptable by Compute Engine API. + """ + if self.cpu_series in { + self.CPUSeries.E2_SMALL, + self.CPUSeries.E2_MICRO, + self.CPUSeries.E2_MEDIUM, + }: + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.memory_mb}" + + if self.extra_memory_used: + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.core_count}-{self.memory_mb}-ext" + + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.core_count}-{self.memory_mb}" + + def short_type_str(self) -> str: + """ + Return machine type in a format without the zone. For example, n2-custom-0-10240. + This format is used to create instance templates. + """ + return str(self).rsplit("/", maxsplit=1)[1] + + @classmethod + def from_str(cls, machine_type: str): + """ + Construct a new object from a string. The string needs to be a valid custom machine type like: + - https://www.googleapis.com/compute/v1/projects/diregapic-mestiv/zones/us-central1-b/machineTypes/e2-custom-4-8192 + - zones/us-central1-b/machineTypes/e2-custom-4-8192 + - e2-custom-4-8192 (in this case, the zone parameter will not be set) + """ + zone = None + if machine_type.startswith("http"): + machine_type = machine_type[machine_type.find("zones/") :] + + if machine_type.startswith("zones/"): + _, zone, _, machine_type = machine_type.split("/") + + extra_mem = machine_type.endswith("-ext") + + if machine_type.startswith("custom"): + cpu = cls.CPUSeries.N1 + _, cores, memory = machine_type.rsplit("-", maxsplit=2) + else: + if extra_mem: + cpu_series, _, cores, memory, _ = machine_type.split("-") + else: + cpu_series, _, cores, memory = machine_type.split("-") + if cpu_series == "n2": + cpu = cls.CPUSeries.N2 + elif cpu_series == "n2d": + cpu = cls.CPUSeries.N2D + elif cpu_series == "e2": + cpu = cls.CPUSeries.E2 + if cores == "micro": + cpu = cls.CPUSeries.E2_MICRO + cores = 2 + elif cores == "small": + cpu = cls.CPUSeries.E2_SMALL + cores = 2 + elif cores == "medium": + cpu = cls.CPUSeries.E2_MEDIUM + cores = 2 + else: + raise RuntimeError("Unknown CPU series.") + + cores = int(cores) + memory = int(memory) + + return cls(zone, cpu, memory, cores) + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_custom_shared_core_instance( + project_id: str, + zone: str, + instance_name: str, + cpu_series: CustomMachineType.CPUSeries, + memory: int, +) -> compute_v1.Instance: + """ + Create a new VM instance with a custom type using shared CPUs. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + cpu_series: the type of CPU you want to use. Pick one value from the CustomMachineType.CPUSeries enum. + For example: CustomMachineType.CPUSeries.E2_MICRO + memory: the amount of memory for the VM instance, in megabytes. + + Return: + Instance object. + """ + assert cpu_series in ( + CustomMachineType.CPUSeries.E2_MICRO, + CustomMachineType.CPUSeries.E2_SMALL, + CustomMachineType.CPUSeries.E2_MEDIUM, + ) + custom_type = CustomMachineType(zone, cpu_series, memory) + + newest_debian = get_image_from_family(project="debian-cloud", family="debian-10") + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + + return create_instance(project_id, zone, instance_name, disks, str(custom_type)) + + +# [END compute_custom_machine_type_create_shared_with_helper] diff --git a/compute/client_library/snippets/instances/custom_machine_types/create_with_helper.py b/compute/client_library/snippets/instances/custom_machine_types/create_with_helper.py new file mode 100644 index 00000000000..18f642549ca --- /dev/null +++ b/compute/client_library/snippets/instances/custom_machine_types/create_with_helper.py @@ -0,0 +1,498 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_custom_machine_type_create_with_helper] +from collections import namedtuple +from enum import Enum +from enum import unique +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def gb_to_mb(value: int) -> int: + return value << 10 + + +class CustomMachineType: + """ + Allows to create custom machine types to be used with the VM instances. + """ + + @unique + class CPUSeries(Enum): + N1 = "custom" + N2 = "n2-custom" + N2D = "n2d-custom" + E2 = "e2-custom" + E2_MICRO = "e2-custom-micro" + E2_SMALL = "e2-custom-small" + E2_MEDIUM = "e2-custom-medium" + + TypeLimits = namedtuple( + "TypeLimits", + [ + "allowed_cores", + "min_mem_per_core", + "max_mem_per_core", + "allow_extra_memory", + "extra_memory_limit", + ], + ) + + # The limits for various CPU types are described on: + # https://cloud.google.com/compute/docs/general-purpose-machines + LIMITS = { + CPUSeries.E2: TypeLimits(frozenset(range(2, 33, 2)), 512, 8192, False, 0), + CPUSeries.E2_MICRO: TypeLimits(frozenset(), 1024, 2048, False, 0), + CPUSeries.E2_SMALL: TypeLimits(frozenset(), 2048, 4096, False, 0), + CPUSeries.E2_MEDIUM: TypeLimits(frozenset(), 4096, 8192, False, 0), + CPUSeries.N2: TypeLimits( + frozenset(range(2, 33, 2)).union(set(range(36, 129, 4))), + 512, + 8192, + True, + gb_to_mb(624), + ), + CPUSeries.N2D: TypeLimits( + frozenset({2, 4, 8, 16, 32, 48, 64, 80, 96}), 512, 8192, True, gb_to_mb(768) + ), + CPUSeries.N1: TypeLimits( + frozenset({1}.union(range(2, 97, 2))), 922, 6656, True, gb_to_mb(624) + ), + } + + def __init__( + self, zone: str, cpu_series: CPUSeries, memory_mb: int, core_count: int = 0 + ): + self.zone = zone + self.cpu_series = cpu_series + self.limits = self.LIMITS[self.cpu_series] + # Shared machine types (e2-small, e2-medium and e2-micro) always have + # 2 vCPUs: https://cloud.google.com/compute/docs/general-purpose-machines#e2_limitations + self.core_count = 2 if self.is_shared() else core_count + self.memory_mb = memory_mb + self._checked = False + self._check_parameters() + self.extra_memory_used = self._check_extra_memory() + + def is_shared(self): + return self.cpu_series in ( + CustomMachineType.CPUSeries.E2_SMALL, + CustomMachineType.CPUSeries.E2_MICRO, + CustomMachineType.CPUSeries.E2_MEDIUM, + ) + + def _check_extra_memory(self) -> bool: + if self._checked: + return self.memory_mb > self.core_count * self.limits.max_mem_per_core + else: + raise RuntimeError( + "You need to call _check_parameters() before calling _check_extra_memory()" + ) + + def _check_parameters(self): + """ + Check whether the requested parameters are allowed. Find more information about limitations of custom machine + types at: https://cloud.google.com/compute/docs/general-purpose-machines#custom_machine_types + """ + # Check the number of cores + if ( + self.limits.allowed_cores + and self.core_count not in self.limits.allowed_cores + ): + raise RuntimeError( + f"Invalid number of cores requested. Allowed number of cores for {self.cpu_series.name} is: {sorted(self.limits.allowed_cores)}" + ) + + # Memory must be a multiple of 256 MB + if self.memory_mb % 256 != 0: + raise RuntimeError("Requested memory must be a multiple of 256 MB.") + + # Check if the requested memory isn't too little + if self.memory_mb < self.core_count * self.limits.min_mem_per_core: + raise RuntimeError( + f"Requested memory is too low. Minimal memory for {self.cpu_series.name} is {self.limits.min_mem_per_core} MB per core." + ) + + # Check if the requested memory isn't too much + if self.memory_mb > self.core_count * self.limits.max_mem_per_core: + if self.limits.allow_extra_memory: + if self.memory_mb > self.limits.extra_memory_limit: + raise RuntimeError( + f"Requested memory is too large.. Maximum memory allowed for {self.cpu_series.name} is {self.limits.extra_memory_limit} MB." + ) + else: + raise RuntimeError( + f"Requested memory is too large.. Maximum memory allowed for {self.cpu_series.name} is {self.limits.max_mem_per_core} MB per core." + ) + + self._checked = True + + def __str__(self) -> str: + """ + Return the custom machine type in form of a string acceptable by Compute Engine API. + """ + if self.cpu_series in { + self.CPUSeries.E2_SMALL, + self.CPUSeries.E2_MICRO, + self.CPUSeries.E2_MEDIUM, + }: + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.memory_mb}" + + if self.extra_memory_used: + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.core_count}-{self.memory_mb}-ext" + + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.core_count}-{self.memory_mb}" + + def short_type_str(self) -> str: + """ + Return machine type in a format without the zone. For example, n2-custom-0-10240. + This format is used to create instance templates. + """ + return str(self).rsplit("/", maxsplit=1)[1] + + @classmethod + def from_str(cls, machine_type: str): + """ + Construct a new object from a string. The string needs to be a valid custom machine type like: + - https://www.googleapis.com/compute/v1/projects/diregapic-mestiv/zones/us-central1-b/machineTypes/e2-custom-4-8192 + - zones/us-central1-b/machineTypes/e2-custom-4-8192 + - e2-custom-4-8192 (in this case, the zone parameter will not be set) + """ + zone = None + if machine_type.startswith("http"): + machine_type = machine_type[machine_type.find("zones/") :] + + if machine_type.startswith("zones/"): + _, zone, _, machine_type = machine_type.split("/") + + extra_mem = machine_type.endswith("-ext") + + if machine_type.startswith("custom"): + cpu = cls.CPUSeries.N1 + _, cores, memory = machine_type.rsplit("-", maxsplit=2) + else: + if extra_mem: + cpu_series, _, cores, memory, _ = machine_type.split("-") + else: + cpu_series, _, cores, memory = machine_type.split("-") + if cpu_series == "n2": + cpu = cls.CPUSeries.N2 + elif cpu_series == "n2d": + cpu = cls.CPUSeries.N2D + elif cpu_series == "e2": + cpu = cls.CPUSeries.E2 + if cores == "micro": + cpu = cls.CPUSeries.E2_MICRO + cores = 2 + elif cores == "small": + cpu = cls.CPUSeries.E2_SMALL + cores = 2 + elif cores == "medium": + cpu = cls.CPUSeries.E2_MEDIUM + cores = 2 + else: + raise RuntimeError("Unknown CPU series.") + + cores = int(cores) + memory = int(memory) + + return cls(zone, cpu, memory, cores) + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_custom_instance( + project_id: str, + zone: str, + instance_name: str, + cpu_series: CustomMachineType.CPUSeries, + core_count: int, + memory: int, +) -> compute_v1.Instance: + """ + Create a new VM instance with a custom machine type. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + cpu_series: the type of CPU you want to use. Select one value from the CustomMachineType.CPUSeries enum. + For example: CustomMachineType.CPUSeries.N2 + core_count: number of CPU cores you want to use. + memory: the amount of memory for the VM instance, in megabytes. + + Return: + Instance object. + """ + assert cpu_series in ( + CustomMachineType.CPUSeries.E2, + CustomMachineType.CPUSeries.N1, + CustomMachineType.CPUSeries.N2, + CustomMachineType.CPUSeries.N2D, + ) + custom_type = CustomMachineType(zone, cpu_series, memory, core_count) + + newest_debian = get_image_from_family(project="debian-cloud", family="debian-10") + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + + return create_instance(project_id, zone, instance_name, disks, str(custom_type)) + + +# [END compute_custom_machine_type_create_with_helper] diff --git a/compute/client_library/snippets/instances/custom_machine_types/create_without_helper.py b/compute/client_library/snippets/instances/custom_machine_types/create_without_helper.py new file mode 100644 index 00000000000..30b2edd9e9a --- /dev/null +++ b/compute/client_library/snippets/instances/custom_machine_types/create_without_helper.py @@ -0,0 +1,322 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_custom_machine_type_create_without_helper] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_custom_instances_no_helper( + project_id: str, zone: str, instance_name: str, core_count: int, memory: int +) -> List[compute_v1.Instance]: + """ + Create 7 new VM instances without using a CustomMachineType helper function. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + core_count: number of CPU cores you want to use. + memory: the amount of memory for the VM instance, in megabytes. + + Returns: + List of Instance objects. + """ + newest_debian = get_image_from_family(project="debian-cloud", family="debian-10") + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + params = [ + ( + f"{instance_name}_n1", + f"zones/{zone}/machineTypes/custom-{core_count}-{memory}", + ), + ( + f"{instance_name}_n2", + f"zones/{zone}/machineTypes/n2-custom-{core_count}-{memory}", + ), + ( + f"{instance_name}_n2d", + f"zones/{zone}/machineTypes/n2d-custom-{core_count}-{memory}", + ), + ( + f"{instance_name}_e2", + f"zones/{zone}/machineTypes/e2-custom-{core_count}-{memory}", + ), + ( + f"{instance_name}_e2_micro", + f"zones/{zone}/machineTypes/e2-custom-micro-{memory}", + ), + ( + f"{instance_name}_e2_small", + f"zones/{zone}/machineTypes/e2-custom-small-{memory}", + ), + ( + f"{instance_name}_e2_medium", + f"zones/{zone}/machineTypes/e2-custom-medium-{memory}", + ), + ] + # The core_count and memory values are not validated anywhere and can be rejected by the API. + instances = [ + create_instance(project_id, zone, name, disks, type) for name, type in params + ] + return instances + + +# [END compute_custom_machine_type_create_without_helper] diff --git a/compute/client_library/snippets/instances/custom_machine_types/extra_mem_no_helper.py b/compute/client_library/snippets/instances/custom_machine_types/extra_mem_no_helper.py new file mode 100644 index 00000000000..de210032724 --- /dev/null +++ b/compute/client_library/snippets/instances/custom_machine_types/extra_mem_no_helper.py @@ -0,0 +1,312 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_custom_machine_type_extra_mem_no_helper] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_custom_instances_extra_mem( + project_id: str, zone: str, instance_name: str, core_count: int, memory: int +) -> List[compute_v1.Instance]: + """ + Create 3 new VM instances with extra memory without using a CustomMachineType helper class. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + core_count: number of CPU cores you want to use. + memory: the amount of memory for the VM instance, in megabytes. + + Returns: + List of Instance objects. + """ + newest_debian = get_image_from_family(project="debian-cloud", family="debian-10") + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + # The core_count and memory values are not validated anywhere and can be rejected by the API. + instances = [ + create_instance( + project_id, + zone, + f"{instance_name}_n1_extra_mem", + disks, + f"zones/{zone}/machineTypes/custom-{core_count}-{memory}-ext", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2_extra_mem", + disks, + f"zones/{zone}/machineTypes/n2-custom-{core_count}-{memory}-ext", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2d_extra_mem", + disks, + f"zones/{zone}/machineTypes/n2d-custom-{core_count}-{memory}-ext", + ), + ] + return instances + + +# [END compute_custom_machine_type_extra_mem_no_helper] diff --git a/compute/client_library/snippets/instances/custom_machine_types/helper_class.py b/compute/client_library/snippets/instances/custom_machine_types/helper_class.py new file mode 100644 index 00000000000..568710aee3f --- /dev/null +++ b/compute/client_library/snippets/instances/custom_machine_types/helper_class.py @@ -0,0 +1,219 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_custom_machine_type_helper_class] +from collections import namedtuple +from enum import Enum +from enum import unique + + +def gb_to_mb(value: int) -> int: + return value << 10 + + +class CustomMachineType: + """ + Allows to create custom machine types to be used with the VM instances. + """ + + @unique + class CPUSeries(Enum): + N1 = "custom" + N2 = "n2-custom" + N2D = "n2d-custom" + E2 = "e2-custom" + E2_MICRO = "e2-custom-micro" + E2_SMALL = "e2-custom-small" + E2_MEDIUM = "e2-custom-medium" + + TypeLimits = namedtuple( + "TypeLimits", + [ + "allowed_cores", + "min_mem_per_core", + "max_mem_per_core", + "allow_extra_memory", + "extra_memory_limit", + ], + ) + + # The limits for various CPU types are described on: + # https://cloud.google.com/compute/docs/general-purpose-machines + LIMITS = { + CPUSeries.E2: TypeLimits(frozenset(range(2, 33, 2)), 512, 8192, False, 0), + CPUSeries.E2_MICRO: TypeLimits(frozenset(), 1024, 2048, False, 0), + CPUSeries.E2_SMALL: TypeLimits(frozenset(), 2048, 4096, False, 0), + CPUSeries.E2_MEDIUM: TypeLimits(frozenset(), 4096, 8192, False, 0), + CPUSeries.N2: TypeLimits( + frozenset(range(2, 33, 2)).union(set(range(36, 129, 4))), + 512, + 8192, + True, + gb_to_mb(624), + ), + CPUSeries.N2D: TypeLimits( + frozenset({2, 4, 8, 16, 32, 48, 64, 80, 96}), 512, 8192, True, gb_to_mb(768) + ), + CPUSeries.N1: TypeLimits( + frozenset({1}.union(range(2, 97, 2))), 922, 6656, True, gb_to_mb(624) + ), + } + + def __init__( + self, zone: str, cpu_series: CPUSeries, memory_mb: int, core_count: int = 0 + ): + self.zone = zone + self.cpu_series = cpu_series + self.limits = self.LIMITS[self.cpu_series] + # Shared machine types (e2-small, e2-medium and e2-micro) always have + # 2 vCPUs: https://cloud.google.com/compute/docs/general-purpose-machines#e2_limitations + self.core_count = 2 if self.is_shared() else core_count + self.memory_mb = memory_mb + self._checked = False + self._check_parameters() + self.extra_memory_used = self._check_extra_memory() + + def is_shared(self): + return self.cpu_series in ( + CustomMachineType.CPUSeries.E2_SMALL, + CustomMachineType.CPUSeries.E2_MICRO, + CustomMachineType.CPUSeries.E2_MEDIUM, + ) + + def _check_extra_memory(self) -> bool: + if self._checked: + return self.memory_mb > self.core_count * self.limits.max_mem_per_core + else: + raise RuntimeError( + "You need to call _check_parameters() before calling _check_extra_memory()" + ) + + def _check_parameters(self): + """ + Check whether the requested parameters are allowed. Find more information about limitations of custom machine + types at: https://cloud.google.com/compute/docs/general-purpose-machines#custom_machine_types + """ + # Check the number of cores + if ( + self.limits.allowed_cores + and self.core_count not in self.limits.allowed_cores + ): + raise RuntimeError( + f"Invalid number of cores requested. Allowed number of cores for {self.cpu_series.name} is: {sorted(self.limits.allowed_cores)}" + ) + + # Memory must be a multiple of 256 MB + if self.memory_mb % 256 != 0: + raise RuntimeError("Requested memory must be a multiple of 256 MB.") + + # Check if the requested memory isn't too little + if self.memory_mb < self.core_count * self.limits.min_mem_per_core: + raise RuntimeError( + f"Requested memory is too low. Minimal memory for {self.cpu_series.name} is {self.limits.min_mem_per_core} MB per core." + ) + + # Check if the requested memory isn't too much + if self.memory_mb > self.core_count * self.limits.max_mem_per_core: + if self.limits.allow_extra_memory: + if self.memory_mb > self.limits.extra_memory_limit: + raise RuntimeError( + f"Requested memory is too large.. Maximum memory allowed for {self.cpu_series.name} is {self.limits.extra_memory_limit} MB." + ) + else: + raise RuntimeError( + f"Requested memory is too large.. Maximum memory allowed for {self.cpu_series.name} is {self.limits.max_mem_per_core} MB per core." + ) + + self._checked = True + + def __str__(self) -> str: + """ + Return the custom machine type in form of a string acceptable by Compute Engine API. + """ + if self.cpu_series in { + self.CPUSeries.E2_SMALL, + self.CPUSeries.E2_MICRO, + self.CPUSeries.E2_MEDIUM, + }: + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.memory_mb}" + + if self.extra_memory_used: + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.core_count}-{self.memory_mb}-ext" + + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.core_count}-{self.memory_mb}" + + def short_type_str(self) -> str: + """ + Return machine type in a format without the zone. For example, n2-custom-0-10240. + This format is used to create instance templates. + """ + return str(self).rsplit("/", maxsplit=1)[1] + + @classmethod + def from_str(cls, machine_type: str): + """ + Construct a new object from a string. The string needs to be a valid custom machine type like: + - https://www.googleapis.com/compute/v1/projects/diregapic-mestiv/zones/us-central1-b/machineTypes/e2-custom-4-8192 + - zones/us-central1-b/machineTypes/e2-custom-4-8192 + - e2-custom-4-8192 (in this case, the zone parameter will not be set) + """ + zone = None + if machine_type.startswith("http"): + machine_type = machine_type[machine_type.find("zones/") :] + + if machine_type.startswith("zones/"): + _, zone, _, machine_type = machine_type.split("/") + + extra_mem = machine_type.endswith("-ext") + + if machine_type.startswith("custom"): + cpu = cls.CPUSeries.N1 + _, cores, memory = machine_type.rsplit("-", maxsplit=2) + else: + if extra_mem: + cpu_series, _, cores, memory, _ = machine_type.split("-") + else: + cpu_series, _, cores, memory = machine_type.split("-") + if cpu_series == "n2": + cpu = cls.CPUSeries.N2 + elif cpu_series == "n2d": + cpu = cls.CPUSeries.N2D + elif cpu_series == "e2": + cpu = cls.CPUSeries.E2 + if cores == "micro": + cpu = cls.CPUSeries.E2_MICRO + cores = 2 + elif cores == "small": + cpu = cls.CPUSeries.E2_SMALL + cores = 2 + elif cores == "medium": + cpu = cls.CPUSeries.E2_MEDIUM + cores = 2 + else: + raise RuntimeError("Unknown CPU series.") + + cores = int(cores) + memory = int(memory) + + return cls(zone, cpu, memory, cores) + + +# [END compute_custom_machine_type_helper_class] diff --git a/compute/client_library/snippets/instances/custom_machine_types/update_memory.py b/compute/client_library/snippets/instances/custom_machine_types/update_memory.py new file mode 100644 index 00000000000..d6e94237466 --- /dev/null +++ b/compute/client_library/snippets/instances/custom_machine_types/update_memory.py @@ -0,0 +1,147 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_custom_machine_type_update_memory] +import sys +import time +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: + """ + This method will wait 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 add_extended_memory_to_instance( + project_id: str, zone: str, instance_name: str, new_memory: int +): + """ + Modify an existing VM to use extended memory. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + new_memory: the amount of memory for the VM instance, in megabytes. + + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + + if not ( + "n1-" in instance.machine_type + or "n2-" in instance.machine_type + or "n2d-" in instance.machine_type + ): + raise RuntimeError("Extra memory is available only for N1, N2 and N2D CPUs.") + + # Make sure that the machine is turned off + if instance.status not in ( + instance.Status.TERMINATED.name, + instance.Status.STOPPED.name, + ): + operation = instance_client.stop( + project=project_id, zone=zone, instance=instance_name + ) + wait_for_extended_operation(operation, "instance stopping") + start = time.time() + while instance.status not in ( + instance.Status.TERMINATED.name, + instance.Status.STOPPED.name, + ): + # Waiting for the instance to be turned off. + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + time.sleep(2) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() + + # Modify the machine definition, remember that extended memory is available only for N1, N2 and N2D CPUs + start, end = instance.machine_type.rsplit("-", maxsplit=1) + instance.machine_type = start + f"-{new_memory}-ext" + # TODO: If you prefer to use the CustomMachineType helper class, uncomment this code and comment the 2 lines above + # Using CustomMachineType helper + # cmt = CustomMachineType.from_str(instance.machine_type) + # cmt.memory_mb = new_memory + # cmt.extra_memory_used = True + # instance.machine_type = str(cmt) + operation = instance_client.update( + project=project_id, + zone=zone, + instance=instance_name, + instance_resource=instance, + ) + wait_for_extended_operation(operation, "instance update") + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +# [END compute_custom_machine_type_update_memory] diff --git a/compute/client_library/snippets/instances/delete.py b/compute/client_library/snippets/instances/delete.py new file mode 100644 index 00000000000..1a8eb3edfb9 --- /dev/null +++ b/compute/client_library/snippets/instances/delete.py @@ -0,0 +1,98 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_delete] +import sys +import time +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: + """ + This method will wait 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 delete_instance(project_id: str, zone: str, machine_name: str) -> None: + """ + Send an instance deletion request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: “us-west3-b” + machine_name: name of the machine you want to delete. + """ + instance_client = compute_v1.InstancesClient() + + print(f"Deleting {machine_name} from {zone}...") + operation = instance_client.delete( + project=project_id, zone=zone, instance=machine_name + ) + wait_for_extended_operation(operation, "instance deletion") + print(f"Instance {machine_name} deleted.") + return + + +# [END compute_instances_delete] diff --git a/compute/client_library/snippets/instances/delete_protection/__init__.py b/compute/client_library/snippets/instances/delete_protection/__init__.py new file mode 100644 index 00000000000..a3ded82a3b6 --- /dev/null +++ b/compute/client_library/snippets/instances/delete_protection/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa diff --git a/compute/client_library/snippets/instances/delete_protection/create.py b/compute/client_library/snippets/instances/delete_protection/create.py new file mode 100644 index 00000000000..7d9bbe9bbfa --- /dev/null +++ b/compute/client_library/snippets/instances/delete_protection/create.py @@ -0,0 +1,290 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_delete_protection_create] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_protected_instance( + project_id: str, zone: str, instance_name: str +) -> compute_v1.Instance: + """ + Create a new VM instance with Debian 10 operating system and delete protection + turned on. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family(project="debian-cloud", family="debian-11") + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + instance = create_instance( + project_id, zone, instance_name, disks, delete_protection=True + ) + return instance + + +# [END compute_delete_protection_create] diff --git a/compute/client_library/snippets/instances/delete_protection/get.py b/compute/client_library/snippets/instances/delete_protection/get.py new file mode 100644 index 00000000000..35bf1dc1f01 --- /dev/null +++ b/compute/client_library/snippets/instances/delete_protection/get.py @@ -0,0 +1,43 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_delete_protection_get] +from google.cloud import compute_v1 + + +def get_delete_protection(project_id: str, zone: str, instance_name: str) -> bool: + """ + Returns the state of delete protection flag of given instance. + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: “us-west3-b” + instance_name: name of the virtual machine to check. + Returns: + The boolean value of the delete protection setting. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + return instance.deletion_protection + + +# [END compute_delete_protection_get] diff --git a/compute/client_library/snippets/instances/delete_protection/set.py b/compute/client_library/snippets/instances/delete_protection/set.py new file mode 100644 index 00000000000..f8c692a3155 --- /dev/null +++ b/compute/client_library/snippets/instances/delete_protection/set.py @@ -0,0 +1,102 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_delete_protection_set] +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: + """ + This method will wait 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 set_delete_protection( + project_id: str, zone: str, instance_name: str, delete_protection: bool +) -> None: + """ + Updates the delete protection setting of given instance. + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: “us-west3-b” + instance_name: name of the instance to update. + delete_protection: boolean value indicating if the virtual machine should be + protected against deletion or not. + """ + instance_client = compute_v1.InstancesClient() + + request = compute_v1.SetDeletionProtectionInstanceRequest() + request.project = project_id + request.zone = zone + request.resource = instance_name + request.deletion_protection = delete_protection + + operation = instance_client.set_deletion_protection(request) + wait_for_extended_operation(operation, "changing delete protection setting") + return + + +# [END compute_delete_protection_set] diff --git a/compute/client_library/snippets/instances/from_instance_template/__init__.py b/compute/client_library/snippets/instances/from_instance_template/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/snippets/instances/from_instance_template/create_from_template.py b/compute/client_library/snippets/instances/from_instance_template/create_from_template.py new file mode 100644 index 00000000000..bcc4b77e970 --- /dev/null +++ b/compute/client_library/snippets/instances/from_instance_template/create_from_template.py @@ -0,0 +1,111 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_create_from_template] +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: + """ + This method will wait 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_instance_from_template( + project_id: str, zone: str, instance_name: str, instance_template_url: str +) -> compute_v1.Instance: + """ + Creates a Compute Engine VM instance from an instance template. + + Args: + project_id: ID or number of the project you want to use. + zone: Name of the zone you want to check, for example: us-west3-b + instance_name: Name of the new instance. + instance_template_url: URL of the instance template used for creating the new instance. + It can be a full or partial URL. + Examples: + - https://www.googleapis.com/compute/v1/projects/project/global/instanceTemplates/example-instance-template + - projects/project/global/instanceTemplates/example-instance-template + - global/instanceTemplates/example-instance-template + + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + instance_insert_request = compute_v1.InsertInstanceRequest() + instance_insert_request.project = project_id + instance_insert_request.zone = zone + instance_insert_request.source_instance_template = instance_template_url + instance_insert_request.instance_resource.name = instance_name + + operation = instance_client.insert(instance_insert_request) + wait_for_extended_operation(operation, "instance creation") + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +# [END compute_instances_create_from_template] diff --git a/compute/client_library/snippets/instances/from_instance_template/create_from_template_with_overrides.py b/compute/client_library/snippets/instances/from_instance_template/create_from_template_with_overrides.py new file mode 100644 index 00000000000..9ff3ba4af4f --- /dev/null +++ b/compute/client_library/snippets/instances/from_instance_template/create_from_template_with_overrides.py @@ -0,0 +1,151 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_create_from_template_with_overrides] +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: + """ + This method will wait 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_instance_from_template_with_overrides( + project_id: str, + zone: str, + instance_name: str, + instance_template_name: str, + machine_type: str, + new_disk_source_image: str, +) -> compute_v1.Instance: + """ + Creates a Compute Engine VM instance from an instance template, changing the machine type and + adding a new disk created from a source image. + + Args: + project_id: ID or number of the project you want to use. + zone: Name of the zone you want to check, for example: us-west3-b + instance_name: Name of the new instance. + instance_template_name: Name of the instance template used for creating the new instance. + machine_type: Machine type you want to set in following format: + "zones/{zone}/machineTypes/{type_name}". For example: + - "zones/europe-west3-c/machineTypes/f1-micro" + - You can find the list of available machine types using: + https://cloud.google.com/sdk/gcloud/reference/compute/machine-types/list + new_disk_source_image: Path the the disk image you want to use for your new + disk. This can be one of the public images + (like "projects/debian-cloud/global/images/family/debian-10") + or a private image you have access to. + For a list of available public images, see the documentation: + http://cloud.google.com/compute/docs/images + + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + instance_template_client = compute_v1.InstanceTemplatesClient() + + # Retrieve an instance template by name. + instance_template = instance_template_client.get( + project=project_id, instance_template=instance_template_name + ) + + # Adjust diskType field of the instance template to use the URL formatting required by instances.insert.diskType + # For instance template, there is only a name, not URL. + for disk in instance_template.properties.disks: + if disk.initialize_params.disk_type: + disk.initialize_params.disk_type = ( + f"zones/{zone}/diskTypes/{disk.initialize_params.disk_type}" + ) + + instance = compute_v1.Instance() + instance.name = instance_name + instance.machine_type = machine_type + instance.disks = list(instance_template.properties.disks) + + new_disk = compute_v1.AttachedDisk() + new_disk.initialize_params.disk_size_gb = 50 + new_disk.initialize_params.source_image = new_disk_source_image + new_disk.auto_delete = True + new_disk.boot = False + new_disk.type_ = "PERSISTENT" + + instance.disks.append(new_disk) + + instance_insert_request = compute_v1.InsertInstanceRequest() + instance_insert_request.project = project_id + instance_insert_request.zone = zone + instance_insert_request.instance_resource = instance + instance_insert_request.source_instance_template = instance_template.self_link + + operation = instance_client.insert(instance_insert_request) + wait_for_extended_operation(operation, "instance creation") + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +# [END compute_instances_create_from_template_with_overrides] diff --git a/compute/client_library/snippets/instances/get.py b/compute/client_library/snippets/instances/get.py new file mode 100644 index 00000000000..427ea19a1d8 --- /dev/null +++ b/compute/client_library/snippets/instances/get.py @@ -0,0 +1,45 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_get] +from google.cloud import compute_v1 + + +def get_instance(project_id: str, zone: str, instance_name: str) -> compute_v1.Instance: + """ + Get information about a VM instance in the given zone in the specified project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: “us-west3-b” + instance_name: name of the VM instance you want to query. + Returns: + An Instance object. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + + return instance + + +# [END compute_instances_get] diff --git a/compute/client_library/snippets/instances/list.py b/compute/client_library/snippets/instances/list.py new file mode 100644 index 00000000000..45830c72ea4 --- /dev/null +++ b/compute/client_library/snippets/instances/list.py @@ -0,0 +1,48 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_list] +from typing import Iterable + +from google.cloud import compute_v1 + + +def list_instances(project_id: str, zone: str) -> Iterable[compute_v1.Instance]: + """ + List all instances in the given zone in the specified project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: “us-west3-b” + Returns: + An iterable collection of Instance objects. + """ + instance_client = compute_v1.InstancesClient() + instance_list = instance_client.list(project=project_id, zone=zone) + + print(f"Instances found in zone {zone}:") + for instance in instance_list: + print(f" - {instance.name} ({instance.machine_type})") + + return instance_list + + +# [END compute_instances_list] diff --git a/compute/client_library/snippets/instances/list_all.py b/compute/client_library/snippets/instances/list_all.py new file mode 100644 index 00000000000..47302fe423a --- /dev/null +++ b/compute/client_library/snippets/instances/list_all.py @@ -0,0 +1,63 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_list_all] +from collections import defaultdict +from typing import Dict, Iterable + +from google.cloud import compute_v1 + + +def list_all_instances( + project_id: str, +) -> Dict[str, Iterable[compute_v1.Instance]]: + """ + Returns a dictionary of all instances present in a project, grouped by their zone. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + Returns: + A dictionary with zone names as keys (in form of "zones/{zone_name}") and + iterable collections of Instance objects as values. + """ + instance_client = compute_v1.InstancesClient() + request = compute_v1.AggregatedListInstancesRequest() + request.project = project_id + # Use the `max_results` parameter to limit the number of results that the API returns per response page. + request.max_results = 50 + + agg_list = instance_client.aggregated_list(request=request) + + all_instances = defaultdict(list) + print("Instances found:") + # Despite using the `max_results` parameter, you don't need to handle the pagination + # yourself. The returned `AggregatedListPager` object handles pagination + # automatically, returning separated pages as you iterate over the results. + for zone, response in agg_list: + if response.instances: + all_instances[zone].extend(response.instances) + print(f" {zone}:") + for instance in response.instances: + print(f" - {instance.name} ({instance.machine_type})") + return all_instances + + +# [END compute_instances_list_all] diff --git a/compute/client_library/snippets/instances/preemptible/__init__.py b/compute/client_library/snippets/instances/preemptible/__init__.py new file mode 100644 index 00000000000..a3ded82a3b6 --- /dev/null +++ b/compute/client_library/snippets/instances/preemptible/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa diff --git a/compute/client_library/snippets/instances/preemptible/create_preemptible.py b/compute/client_library/snippets/instances/preemptible/create_preemptible.py new file mode 100644 index 00000000000..5816d39f4b0 --- /dev/null +++ b/compute/client_library/snippets/instances/preemptible/create_preemptible.py @@ -0,0 +1,287 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_preemptible_create] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_preemptible_instance( + project_id: str, zone: str, instance_name: str +) -> compute_v1.Instance: + """ + Create a new preemptible VM instance with Debian 10 operating system. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family(project="debian-cloud", family="debian-11") + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + instance = create_instance(project_id, zone, instance_name, disks, preemptible=True) + return instance + + +# [END compute_preemptible_create] diff --git a/compute/client_library/snippets/instances/preemptible/is_preemptible.py b/compute/client_library/snippets/instances/preemptible/is_preemptible.py new file mode 100644 index 00000000000..8a0c966fdfc --- /dev/null +++ b/compute/client_library/snippets/instances/preemptible/is_preemptible.py @@ -0,0 +1,43 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_preemptible_check] +from google.cloud import compute_v1 + + +def is_preemptible(project_id: str, zone: str, instance_name: str) -> bool: + """ + Check if a given instance is preemptible or not. + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: "us-west3-b" + instance_name: name of the virtual machine to check. + Returns: + The preemptible status of the instance. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + return instance.scheduling.preemptible + + +# [END compute_preemptible_check] diff --git a/compute/client_library/snippets/instances/preemptible/preemption_history.py b/compute/client_library/snippets/instances/preemptible/preemption_history.py new file mode 100644 index 00000000000..23a1f79745d --- /dev/null +++ b/compute/client_library/snippets/instances/preemptible/preemption_history.py @@ -0,0 +1,87 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_preemptible_history] +import datetime +from typing import List, Tuple + +from google.cloud import compute_v1 +from google.cloud.compute_v1.services.zone_operations import pagers + + +def list_zone_operations( + project_id: str, zone: str, filter: str = "" +) -> pagers.ListPager: + """ + List all recent operations the happened in given zone in a project. Optionally filter those + operations by providing a filter. More about using the filter can be found here: + https://cloud.google.com/compute/docs/reference/rest/v1/zoneOperations/list + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: "us-west3-b" + filter: filter string to be used for this listing operation. + Returns: + List of preemption operations in given zone. + """ + operation_client = compute_v1.ZoneOperationsClient() + request = compute_v1.ListZoneOperationsRequest() + request.project = project_id + request.zone = zone + request.filter = filter + + return operation_client.list(request) + + +def preemption_history( + project_id: str, zone: str, instance_name: str = None +) -> List[Tuple[str, datetime.datetime]]: + """ + Get a list of preemption operations from given zone in a project. Optionally limit + the results to instance name. + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: "us-west3-b" + instance_name: name of the virtual machine to look for. + Returns: + List of preemption operations in given zone. + """ + if instance_name: + filter = ( + f'operationType="compute.instances.preempted" ' + f"AND targetLink:instances/{instance_name}" + ) + else: + filter = 'operationType="compute.instances.preempted"' + + history = [] + + for operation in list_zone_operations(project_id, zone, filter): + this_instance_name = operation.target_link.rsplit("/", maxsplit=1)[1] + if instance_name and this_instance_name == instance_name: + # The filter used is not 100% accurate, it's `contains` not `equals` + # So we need to check the name to make sure it's the one we want. + moment = datetime.datetime.fromisoformat(operation.insert_time) + history.append((instance_name, moment)) + + return history + + +# [END compute_preemptible_history] diff --git a/compute/client_library/snippets/instances/reset.py b/compute/client_library/snippets/instances/reset.py new file mode 100644 index 00000000000..110d84eb1d1 --- /dev/null +++ b/compute/client_library/snippets/instances/reset.py @@ -0,0 +1,97 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_reset_instance] +import sys +import time +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: + """ + This method will wait 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 reset_instance(project_id: str, zone: str, instance_name: str) -> None: + """ + Resets a stopped Google Compute Engine instance (with unencrypted disks). + Args: + project_id: project ID or project number of the Cloud project your instance belongs to. + zone: name of the zone your instance belongs to. + instance_name: name of the instance your want to reset. + """ + instance_client = compute_v1.InstancesClient() + + operation = instance_client.reset( + project=project_id, zone=zone, instance=instance_name + ) + + wait_for_extended_operation(operation, "instance reset") + + return + + +# [END compute_reset_instance] diff --git a/compute/client_library/snippets/instances/resume.py b/compute/client_library/snippets/instances/resume.py new file mode 100644 index 00000000000..04d08705c93 --- /dev/null +++ b/compute/client_library/snippets/instances/resume.py @@ -0,0 +1,105 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_resume_instance] +import sys +import time +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: + """ + This method will wait 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 resume_instance(project_id: str, zone: str, instance_name: str) -> None: + """ + Resume a suspended Google Compute Engine instance (with unencrypted disks). + Args: + project_id: project ID or project number of the Cloud project your instance belongs to. + zone: name of the zone your instance belongs to. + instance_name: name of the instance you want to resume. + """ + instance_client = compute_v1.InstancesClient() + + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + if instance.status != compute_v1.Instance.Status.SUSPENDED.name: + raise RuntimeError( + f"Only suspended instances can be resumed. " + f"Instance {instance_name} is in {instance.status} state." + ) + + operation = instance_client.resume( + project=project_id, zone=zone, instance=instance_name + ) + + wait_for_extended_operation(operation, "instance resumption") + return + + +# [END compute_resume_instance] diff --git a/compute/client_library/snippets/instances/spot/__init__.py b/compute/client_library/snippets/instances/spot/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/snippets/instances/spot/create.py b/compute/client_library/snippets/instances/spot/create.py new file mode 100644 index 00000000000..ddf437f6bfc --- /dev/null +++ b/compute/client_library/snippets/instances/spot/create.py @@ -0,0 +1,287 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_spot_create] +import re +import sys +from typing import Any, List +import warnings + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + """ + Retrieve the newest image that is part of a given family in a project. + + Args: + project: project ID or project number of the Cloud project you want to get image from. + family: name of the image family you want to get image from. + + Returns: + An Image object. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_image = image_client.get_from_family(project=project, family=family) + return newest_image + + +def disk_from_image( + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = True, +) -> compute_v1.AttachedDisk: + """ + Create an AttachedDisk object to be used in VM instance creation. Uses an image as the + source for the new disk. + + Args: + 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 + boot: boolean flag indicating whether this disk should be used as a boot disk of an instance + source_image: source image to use when creating this disk. You must have read access to this disk. This can be one + of the publicly available images or an image from one of your projects. + This value uses the following format: "projects/{project_name}/global/images/{image_name}" + auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it + + Returns: + AttachedDisk object configured to be created using the specified image. + """ + boot_disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = source_image + initialize_params.disk_size_gb = disk_size_gb + initialize_params.disk_type = disk_type + boot_disk.initialize_params = initialize_params + # Remember to set auto_delete to True if you want the disk to be deleted when you delete + # your VM instance. + boot_disk.auto_delete = auto_delete + boot_disk.boot = boot + return boot_disk + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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_instance( + project_id: str, + zone: str, + instance_name: str, + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + internal_ip: str = None, + external_access: bool = False, + external_ipv4: str = None, + accelerators: List[compute_v1.AcceleratorConfig] = None, + preemptible: bool = False, + spot: bool = False, + instance_termination_action: str = "STOP", + custom_hostname: str = None, + delete_protection: bool = False, +) -> compute_v1.Instance: + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + network_link: name of the network you want the new instance to use. + For example: "global/networks/default" represents the network + named "default", which is created automatically for each project. + subnetwork_link: name of the subnetwork you want the new instance to use. + This value uses the following format: + "regions/{region}/subnetworks/{subnetwork_name}" + internal_ip: internal IP address you want to assign to the new instance. + By default, a free address from the pool of available internal IP addresses of + used subnet will be used. + external_access: boolean flag indicating if the instance should have an external IPv4 + address assigned. + external_ipv4: external IPv4 address to be assigned to this instance. If you specify + an external IP address, it must live in the same region as the zone of the instance. + This setting requires `external_access` to be set to True to work. + accelerators: a list of AcceleratorConfig objects describing the accelerators that will + be attached to the new instance. + preemptible: boolean value indicating if the new instance should be preemptible + or not. Preemptible VMs have been deprecated and you should now use Spot VMs. + spot: boolean value indicating if the new instance should be a Spot VM or not. + instance_termination_action: What action should be taken once a Spot VM is terminated. + Possible values: "STOP", "DELETE" + custom_hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + delete_protection: boolean value indicating if the new virtual machine should be + protected against deletion or not. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Use the network interface provided in the network_link argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link + + if internal_ip: + network_interface.network_i_p = internal_ip + + if external_access: + access = compute_v1.AccessConfig() + access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name + access.name = "External NAT" + access.network_tier = access.NetworkTier.PREMIUM.name + if external_ipv4: + access.nat_i_p = external_ipv4 + network_interface.access_configs = [access] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.network_interfaces = [network_interface] + instance.name = instance_name + instance.disks = disks + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + + if accelerators: + instance.guest_accelerators = accelerators + + if preemptible: + # Set the preemptible setting + warnings.warn( + "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning + ) + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + if spot: + # Set the Spot VM setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.provisioning_model = ( + compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + instance.scheduling.instance_termination_action = instance_termination_action + + if custom_hostname is not None: + # Set the custom hostname for the instance + instance.hostname = custom_hostname + + if delete_protection: + # Set the delete protection bit + instance.deletion_protection = True + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print(f"Creating the {instance_name} instance in {zone}...") + + operation = instance_client.insert(request=request) + + wait_for_extended_operation(operation, "instance creation") + + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +def create_spot_instance( + project_id: str, zone: str, instance_name: str +) -> compute_v1.Instance: + """ + Create a new Spot VM instance with Debian 10 operating system. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + + Returns: + Instance object. + """ + newest_debian = get_image_from_family(project="debian-cloud", family="debian-11") + disk_type = f"zones/{zone}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + instance = create_instance(project_id, zone, instance_name, disks, spot=True) + return instance + + +# [END compute_spot_create] diff --git a/compute/client_library/snippets/instances/spot/is_spot_vm.py b/compute/client_library/snippets/instances/spot/is_spot_vm.py new file mode 100644 index 00000000000..4cb83657094 --- /dev/null +++ b/compute/client_library/snippets/instances/spot/is_spot_vm.py @@ -0,0 +1,46 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_spot_check] +from google.cloud import compute_v1 + + +def is_spot_vm(project_id: str, zone: str, instance_name: str) -> bool: + """ + Check if a given instance is Spot VM or not. + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone you want to use. For example: "us-west3-b" + instance_name: name of the virtual machine to check. + Returns: + The Spot VM status of the instance. + """ + instance_client = compute_v1.InstancesClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + return ( + instance.scheduling.provisioning_model + == compute_v1.Scheduling.ProvisioningModel.SPOT.name + ) + + +# [END compute_spot_check] diff --git a/compute/client_library/snippets/instances/start.py b/compute/client_library/snippets/instances/start.py new file mode 100644 index 00000000000..70e3e34481d --- /dev/null +++ b/compute/client_library/snippets/instances/start.py @@ -0,0 +1,96 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_start_instance] +import sys +import time +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: + """ + This method will wait 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 start_instance(project_id: str, zone: str, instance_name: str) -> None: + """ + Starts a stopped Google Compute Engine instance (with unencrypted disks). + Args: + project_id: project ID or project number of the Cloud project your instance belongs to. + zone: name of the zone your instance belongs to. + instance_name: name of the instance your want to start. + """ + instance_client = compute_v1.InstancesClient() + + operation = instance_client.start( + project=project_id, zone=zone, instance=instance_name + ) + + wait_for_extended_operation(operation, "instance start") + return + + +# [END compute_start_instance] diff --git a/compute/client_library/snippets/instances/start_encrypted.py b/compute/client_library/snippets/instances/start_encrypted.py new file mode 100644 index 00000000000..ecfa1cd2620 --- /dev/null +++ b/compute/client_library/snippets/instances/start_encrypted.py @@ -0,0 +1,118 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_start_enc_instance] +import sys +import time +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: + """ + This method will wait 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 start_instance_with_encryption_key( + project_id: str, zone: str, instance_name: str, key: bytes +): + """ + Starts a stopped Google Compute Engine instance (with encrypted disks). + Args: + project_id: project ID or project number of the Cloud project your instance belongs to. + zone: name of the zone your instance belongs to. + instance_name: name of the instance your want to start. + key: bytes object representing a raw base64 encoded key to your machines boot disk. + For more information about disk encryption see: + https://cloud.google.com/compute/docs/disks/customer-supplied-encryption#specifications + """ + instance_client = compute_v1.InstancesClient() + + instance_data = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + + # Prepare the information about disk encryption + disk_data = compute_v1.CustomerEncryptionKeyProtectedDisk() + disk_data.source = instance_data.disks[0].source + disk_data.disk_encryption_key = compute_v1.CustomerEncryptionKey() + # Use raw_key to send over the key to unlock the disk + # To use a key stored in KMS, you need to provide `kms_key_name` and `kms_key_service_account` + disk_data.disk_encryption_key.raw_key = key + enc_data = compute_v1.InstancesStartWithEncryptionKeyRequest() + enc_data.disks = [disk_data] + + operation = instance_client.start_with_encryption_key( + project=project_id, + zone=zone, + instance=instance_name, + instances_start_with_encryption_key_request_resource=enc_data, + ) + + wait_for_extended_operation(operation, "instance start (with encrypted disk)") + return + + +# [END compute_start_enc_instance] diff --git a/compute/client_library/snippets/instances/stop.py b/compute/client_library/snippets/instances/stop.py new file mode 100644 index 00000000000..1b675c4f115 --- /dev/null +++ b/compute/client_library/snippets/instances/stop.py @@ -0,0 +1,95 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_stop_instance] +import sys +import time +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: + """ + This method will wait 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 stop_instance(project_id: str, zone: str, instance_name: str) -> None: + """ + Stops a running Google Compute Engine instance. + Args: + project_id: project ID or project number of the Cloud project your instance belongs to. + zone: name of the zone your instance belongs to. + instance_name: name of the instance your want to stop. + """ + instance_client = compute_v1.InstancesClient() + + operation = instance_client.stop( + project=project_id, zone=zone, instance=instance_name + ) + wait_for_extended_operation(operation, "instance stopping") + return + + +# [END compute_stop_instance] diff --git a/compute/client_library/snippets/instances/suspend.py b/compute/client_library/snippets/instances/suspend.py new file mode 100644 index 00000000000..4a7c373dafc --- /dev/null +++ b/compute/client_library/snippets/instances/suspend.py @@ -0,0 +1,96 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_suspend_instance] +import sys +import time +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: + """ + This method will wait 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 suspend_instance(project_id: str, zone: str, instance_name: str) -> None: + """ + Suspend a running Google Compute Engine instance. + Args: + project_id: project ID or project number of the Cloud project your instance belongs to. + zone: name of the zone your instance belongs to. + instance_name: name of the instance you want to suspend. + """ + instance_client = compute_v1.InstancesClient() + + operation = instance_client.suspend( + project=project_id, zone=zone, instance=instance_name + ) + + wait_for_extended_operation(operation, "suspend instance") + return + + +# [END compute_suspend_instance] diff --git a/compute/client_library/snippets/operations/__init__.py b/compute/client_library/snippets/operations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/snippets/operations/operation_check.py b/compute/client_library/snippets/operations/operation_check.py new file mode 100644 index 00000000000..d136372c39c --- /dev/null +++ b/compute/client_library/snippets/operations/operation_check.py @@ -0,0 +1,68 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_operation_check] +from google.cloud import compute_v1 + + +def wait_for_operation( + operation: compute_v1.Operation, project_id: str +) -> compute_v1.Operation: + """ + This method waits for an operation to be completed. Calling this function + will block until the operation is finished. + + Args: + operation: The Operation object representing the operation you want to + wait on. + project_id: project ID or project number of the Cloud project you want to use. + + Returns: + Finished Operation object. + """ + kwargs = {"project": project_id, "operation": operation.name} + if operation.zone: + client = compute_v1.ZoneOperationsClient() + # Operation.zone is a full URL address of a zone, so we need to extract just the name + kwargs["zone"] = operation.zone.rsplit("/", maxsplit=1)[1] + elif operation.region: + client = compute_v1.RegionOperationsClient() + # Operation.region is a full URL address of a region, so we need to extract just the name + kwargs["region"] = operation.region.rsplit("/", maxsplit=1)[1] + else: + client = compute_v1.GlobalOperationsClient() + return client.wait(**kwargs) + + +# [END compute_instances_operation_check] diff --git a/compute/client_library/snippets/routes/create.py b/compute/client_library/snippets/routes/create.py new file mode 100644 index 00000000000..abc3c77e5b3 --- /dev/null +++ b/compute/client_library/snippets/routes/create.py @@ -0,0 +1,152 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_route_create] +import sys +from typing import Any, Optional + +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: + """ + This method will wait 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_route( + project_id: str, + network: str, + route_name: str, + destination_range: str, + *, + next_hop_gateway: Optional[str] = None, + next_hop_ip: Optional[str] = None, + next_hop_instance: Optional[str] = None, + next_hop_vpn_tunnel: Optional[str] = None, + next_hop_ilb: Optional[str] = None, +) -> compute_v1.Route: + """ + Create a new route in selected network by providing a destination and next hop name. + + Note: The set of {next_hop_gateway, next_hop_ip, next_hop_instance, next_hop_vpn_tunnel, + next_hop_ilb} is exclusive, you and only specify one of those parameters. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + network: name of the network the route will be created in. Available name formats: + * https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network} + * projects/{project_id}/global/networks/{network} + * global/networks/{network} + route_name: name of the new route. + destination_range: range of destination IPs this route should be applied to. E.g. 10.0.0.0/16. + next_hop_gateway: name of the gateway the traffic should be directed to. + next_hop_ip: IP address the traffic should be directed to. + next_hop_instance: name of the instance the traffic should be directed to. Name format: + "projects/{project}/zones/{zone}/instances/{instance_name}" + next_hop_vpn_tunnel: name of the VPN tunnel the traffic should be directed to. Name format: + "projects/{project}/regions/{region}/vpnTunnels/{vpn_tunnel_name}" + next_hop_ilb: name of a forwarding rule of the Internal Load Balancer the traffic + should be directed to. Name format: + "projects/{project}/regions/{region}/forwardingRules/{forwarding_rule_region}" + + Returns: + A new compute_v1.Route object. + """ + excl_args = { + next_hop_instance, + next_hop_ilb, + next_hop_vpn_tunnel, + next_hop_gateway, + next_hop_ip, + } + args_set = sum(1 if arg is not None else 0 for arg in excl_args) + + if args_set != 1: + raise RuntimeError("You must specify exactly one next_hop_* parameter.") + + route = compute_v1.Route() + route.name = route_name + route.network = network + route.dest_range = destination_range + + if next_hop_gateway: + route.next_hop_gateway = next_hop_gateway + elif next_hop_ip: + route.next_hop_ip = next_hop_ip + elif next_hop_instance: + route.next_hop_instance = next_hop_instance + elif next_hop_vpn_tunnel: + route.next_hop_vpn_tunnel = next_hop_vpn_tunnel + elif next_hop_ilb: + route.next_hop_ilb = next_hop_ilb + + route_client = compute_v1.RoutesClient() + operation = route_client.insert(project=project_id, route_resource=route) + + wait_for_extended_operation(operation, "route creation") + + return route_client.get(project=project_id, route=route_name) + + +# [END compute_route_create] diff --git a/compute/client_library/snippets/routes/create_kms_route.py b/compute/client_library/snippets/routes/create_kms_route.py new file mode 100644 index 00000000000..eeb2cffc876 --- /dev/null +++ b/compute/client_library/snippets/routes/create_kms_route.py @@ -0,0 +1,194 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_create_route_windows_activation] +import sys +from typing import Any, Optional + +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: + """ + This method will wait 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_route( + project_id: str, + network: str, + route_name: str, + destination_range: str, + *, + next_hop_gateway: Optional[str] = None, + next_hop_ip: Optional[str] = None, + next_hop_instance: Optional[str] = None, + next_hop_vpn_tunnel: Optional[str] = None, + next_hop_ilb: Optional[str] = None, +) -> compute_v1.Route: + """ + Create a new route in selected network by providing a destination and next hop name. + + Note: The set of {next_hop_gateway, next_hop_ip, next_hop_instance, next_hop_vpn_tunnel, + next_hop_ilb} is exclusive, you and only specify one of those parameters. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + network: name of the network the route will be created in. Available name formats: + * https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network} + * projects/{project_id}/global/networks/{network} + * global/networks/{network} + route_name: name of the new route. + destination_range: range of destination IPs this route should be applied to. E.g. 10.0.0.0/16. + next_hop_gateway: name of the gateway the traffic should be directed to. + next_hop_ip: IP address the traffic should be directed to. + next_hop_instance: name of the instance the traffic should be directed to. Name format: + "projects/{project}/zones/{zone}/instances/{instance_name}" + next_hop_vpn_tunnel: name of the VPN tunnel the traffic should be directed to. Name format: + "projects/{project}/regions/{region}/vpnTunnels/{vpn_tunnel_name}" + next_hop_ilb: name of a forwarding rule of the Internal Load Balancer the traffic + should be directed to. Name format: + "projects/{project}/regions/{region}/forwardingRules/{forwarding_rule_region}" + + Returns: + A new compute_v1.Route object. + """ + excl_args = { + next_hop_instance, + next_hop_ilb, + next_hop_vpn_tunnel, + next_hop_gateway, + next_hop_ip, + } + args_set = sum(1 if arg is not None else 0 for arg in excl_args) + + if args_set != 1: + raise RuntimeError("You must specify exactly one next_hop_* parameter.") + + route = compute_v1.Route() + route.name = route_name + route.network = network + route.dest_range = destination_range + + if next_hop_gateway: + route.next_hop_gateway = next_hop_gateway + elif next_hop_ip: + route.next_hop_ip = next_hop_ip + elif next_hop_instance: + route.next_hop_instance = next_hop_instance + elif next_hop_vpn_tunnel: + route.next_hop_vpn_tunnel = next_hop_vpn_tunnel + elif next_hop_ilb: + route.next_hop_ilb = next_hop_ilb + + route_client = compute_v1.RoutesClient() + operation = route_client.insert(project=project_id, route_resource=route) + + wait_for_extended_operation(operation, "route creation") + + return route_client.get(project=project_id, route=route_name) + + +def create_route_to_windows_activation_host( + project_id: str, network: str, route_name: str +) -> compute_v1.Route: + """ + If you have Windows instances without external IP addresses, + you must also enable Private Google Access so that instances + with only internal IP addresses can send traffic to the external + IP address for kms.windows.googlecloud.com. + More infromation: https://cloud.google.com/vpc/docs/configure-private-google-access#enabling + + Args: + project_id: project ID or project number of the Cloud project you want to use. + network: name of the network the route will be created in. Available name formats: + * https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network} + * projects/{project_id}/global/networks/{network} + * global/networks/{network} + route_name: name of the new route. + + Returns: + A new compute_v1.Route object. + """ + return create_route( + project_id=project_id, + network=network, + route_name=route_name, + destination_range="35.190.247.13/32", + next_hop_gateway=f"projects/{project_id}/global/gateways/default-internet-gateway", + ) + + +# [END compute_create_route_windows_activation] diff --git a/compute/client_library/snippets/routes/delete.py b/compute/client_library/snippets/routes/delete.py new file mode 100644 index 00000000000..6ef1de95b1c --- /dev/null +++ b/compute/client_library/snippets/routes/delete.py @@ -0,0 +1,94 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_route_delete] +import sys +from typing import Any, NoReturn + +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: + """ + This method will wait 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 delete_route(project_id: str, route_name: str) -> NoReturn: + """ + Delete a route in project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + route_name: name of the route to delete. + """ + + route_client = compute_v1.RoutesClient() + operation = route_client.delete(project=project_id, route=route_name) + + wait_for_extended_operation(operation, "route deletion") + + return + + +# [END compute_route_delete] diff --git a/compute/client_library/snippets/routes/list.py b/compute/client_library/snippets/routes/list.py new file mode 100644 index 00000000000..b4f83fb07d3 --- /dev/null +++ b/compute/client_library/snippets/routes/list.py @@ -0,0 +1,45 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_route_list] +from typing import Iterable + +from google.cloud import compute_v1 + + +def list_routes( + project_id: str, +) -> Iterable[compute_v1.Route]: + """ + Lists routes in project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + + Returns: + An iterable collection of routes found in given project. + """ + + route_client = compute_v1.RoutesClient() + return route_client.list(project=project_id) + + +# [END compute_route_list] diff --git a/compute/client_library/snippets/snapshots/__init__.py b/compute/client_library/snippets/snapshots/__init__.py new file mode 100644 index 00000000000..4bbe0ffdb06 --- /dev/null +++ b/compute/client_library/snippets/snapshots/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/compute/client_library/snippets/snapshots/create.py b/compute/client_library/snippets/snapshots/create.py new file mode 100644 index 00000000000..fb1b75c0fa5 --- /dev/null +++ b/compute/client_library/snippets/snapshots/create.py @@ -0,0 +1,145 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_snapshot_create] +import sys +from typing import Any, Optional + +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: + """ + This method will wait 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_snapshot( + project_id: str, + disk_name: str, + snapshot_name: str, + *, + zone: Optional[str] = None, + region: Optional[str] = None, + location: Optional[str] = None, + disk_project_id: Optional[str] = None, +) -> compute_v1.Snapshot: + """ + Create a snapshot of a disk. + + You need to pass `zone` or `region` parameter relevant to the disk you want to + snapshot, but not both. Pass `zone` parameter for zonal disks and `region` for + regional disks. + + Args: + project_id: project ID or project number of the Cloud project you want + to use to store the snapshot. + disk_name: name of the disk you want to snapshot. + snapshot_name: name of the snapshot to be created. + zone: name of the zone in which is the disk you want to snapshot (for zonal disks). + region: name of the region in which is the disk you want to snapshot (for regional disks). + location: The Cloud Storage multi-region or the Cloud Storage region where you + want to store your snapshot. + You can specify only one storage location. Available locations: + https://cloud.google.com/storage/docs/locations#available-locations + disk_project_id: project ID or project number of the Cloud project that + hosts the disk you want to snapshot. If not provided, will look for + the disk in the `project_id` project. + + Returns: + The new snapshot instance. + """ + if zone is None and region is None: + raise RuntimeError( + "You need to specify `zone` or `region` for this function to work." + ) + if zone is not None and region is not None: + raise RuntimeError("You can't set both `zone` and `region` parameters.") + + if disk_project_id is None: + disk_project_id = project_id + + if zone is not None: + disk_client = compute_v1.DisksClient() + disk = disk_client.get(project=disk_project_id, zone=zone, disk=disk_name) + else: + regio_disk_client = compute_v1.RegionDisksClient() + disk = regio_disk_client.get( + project=disk_project_id, region=region, disk=disk_name + ) + + snapshot = compute_v1.Snapshot() + snapshot.source_disk = disk.self_link + snapshot.name = snapshot_name + if location: + snapshot.storage_locations = [location] + + snapshot_client = compute_v1.SnapshotsClient() + operation = snapshot_client.insert(project=project_id, snapshot_resource=snapshot) + + wait_for_extended_operation(operation, "snapshot creation") + + return snapshot_client.get(project=project_id, snapshot=snapshot_name) + + +# [END compute_snapshot_create] diff --git a/compute/client_library/snippets/snapshots/delete.py b/compute/client_library/snippets/snapshots/delete.py new file mode 100644 index 00000000000..5735dfba25e --- /dev/null +++ b/compute/client_library/snippets/snapshots/delete.py @@ -0,0 +1,94 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_snapshot_delete] +import sys +from typing import Any, NoReturn + +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: + """ + This method will wait 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 delete_snapshot(project_id: str, snapshot_name: str) -> NoReturn: + """ + Delete a snapshot of a disk. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + snapshot_name: name of the snapshot to delete. + """ + + snapshot_client = compute_v1.SnapshotsClient() + operation = snapshot_client.delete(project=project_id, snapshot=snapshot_name) + + wait_for_extended_operation(operation, "snapshot deletion") + + return + + +# [END compute_snapshot_delete] diff --git a/compute/client_library/snippets/snapshots/delete_by_filter.py b/compute/client_library/snippets/snapshots/delete_by_filter.py new file mode 100644 index 00000000000..a71bda1de45 --- /dev/null +++ b/compute/client_library/snippets/snapshots/delete_by_filter.py @@ -0,0 +1,128 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_snapshot_delete_by_filter] +import sys +from typing import Any, Iterable, NoReturn + +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: + """ + This method will wait 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 delete_snapshot(project_id: str, snapshot_name: str) -> NoReturn: + """ + Delete a snapshot of a disk. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + snapshot_name: name of the snapshot to delete. + """ + + snapshot_client = compute_v1.SnapshotsClient() + operation = snapshot_client.delete(project=project_id, snapshot=snapshot_name) + + wait_for_extended_operation(operation, "snapshot deletion") + + return + + +def list_snapshots(project_id: str, filter: str = "") -> Iterable[compute_v1.Snapshot]: + """ + List snapshots from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + filter: filter to be applied when listing snapshots. Learn more about filters here: + https://cloud.google.com/python/docs/reference/compute/latest/google.cloud.compute_v1.types.ListSnapshotsRequest + + Returns: + An iterable containing all Snapshots that match the provided filter. + """ + + snapshot_client = compute_v1.SnapshotsClient() + request = compute_v1.ListSnapshotsRequest() + request.project = project_id + request.filter = filter + + return snapshot_client.list(request) + + +def delete_snapshots_by_filter(project_id: str, filter: str): + """ + Deletes all snapshots in project that meet the filter criteria. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + filter: filter to be applied when looking for snapshots for deletion. + """ + for snapshot in list_snapshots(project_id, filter): + delete_snapshot(project_id, snapshot.name) + + +# [END compute_snapshot_delete_by_filter] diff --git a/compute/client_library/snippets/snapshots/get.py b/compute/client_library/snippets/snapshots/get.py new file mode 100644 index 00000000000..bb6fa1d38a0 --- /dev/null +++ b/compute/client_library/snippets/snapshots/get.py @@ -0,0 +1,45 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_snapshot_get] +from typing import Iterable + +from google.cloud import compute_v1 + + +def get_snapshot(project_id: str, snapshot_name: str) -> compute_v1.Snapshot: + """ + Get information about a Snapshot. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + snapshot_name: the name of the snapshot you want to look up. + + Returns: + A Snapshot object. + """ + + snapshot_client = compute_v1.SnapshotsClient() + + return snapshot_client.get(project=project_id, snapshot=snapshot_name) + + +# [END compute_snapshot_get] diff --git a/compute/client_library/snippets/snapshots/list.py b/compute/client_library/snippets/snapshots/list.py new file mode 100644 index 00000000000..4c5a89761e9 --- /dev/null +++ b/compute/client_library/snippets/snapshots/list.py @@ -0,0 +1,49 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_snapshot_list] +from typing import Iterable + +from google.cloud import compute_v1 + + +def list_snapshots(project_id: str, filter: str = "") -> Iterable[compute_v1.Snapshot]: + """ + List snapshots from a project. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + filter: filter to be applied when listing snapshots. Learn more about filters here: + https://cloud.google.com/python/docs/reference/compute/latest/google.cloud.compute_v1.types.ListSnapshotsRequest + + Returns: + An iterable containing all Snapshots that match the provided filter. + """ + + snapshot_client = compute_v1.SnapshotsClient() + request = compute_v1.ListSnapshotsRequest() + request.project = project_id + request.filter = filter + + return snapshot_client.list(request) + + +# [END compute_snapshot_list] diff --git a/compute/client_library/snippets/tests/__init__.py b/compute/client_library/snippets/tests/__init__.py new file mode 100644 index 00000000000..4bbe0ffdb06 --- /dev/null +++ b/compute/client_library/snippets/tests/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/compute/client_library/snippets/tests/test_bulk.py b/compute/client_library/snippets/tests/test_bulk.py new file mode 100644 index 00000000000..2d270f1245a --- /dev/null +++ b/compute/client_library/snippets/tests/test_bulk.py @@ -0,0 +1,76 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import uuid + +import google.auth +from google.cloud import compute_v1 +import pytest + +from ..instances.bulk_insert import create_five_instances +from ..instances.delete import delete_instance + +PROJECT = google.auth.default()[1] +INSTANCE_ZONE = "australia-southeast1-a" + + +@pytest.fixture +def instance_template(): + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = ( + "projects/debian-cloud/global/images/family/debian-11" + ) + initialize_params.disk_size_gb = 25 + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + + network_interface = compute_v1.NetworkInterface() + network_interface.name = "global/networks/default" + + template = compute_v1.InstanceTemplate() + template.name = "test-template-" + uuid.uuid4().hex[:10] + template.properties.disks = [disk] + template.properties.machine_type = "n1-standard-4" + template.properties.network_interfaces = [network_interface] + + template_client = compute_v1.InstanceTemplatesClient() + operation_client = compute_v1.GlobalOperationsClient() + op = template_client.insert_unary( + project=PROJECT, instance_template_resource=template + ) + operation_client.wait(project=PROJECT, operation=op.name) + + template = template_client.get(project=PROJECT, instance_template=template.name) + + yield template + + op = template_client.delete_unary(project=PROJECT, instance_template=template.name) + operation_client.wait(project=PROJECT, operation=op.name) + + +def test_bulk_create(instance_template): + name_pattern = "i-##-" + uuid.uuid4().hex[:5] + + instances = create_five_instances(PROJECT, INSTANCE_ZONE, instance_template.name, + name_pattern) + + names = [instance.name for instance in instances] + try: + for i in range(1, 6): + name = name_pattern.replace('##', f"0{i}") + assert name in names + finally: + for name in names: + delete_instance(PROJECT, INSTANCE_ZONE, name) diff --git a/compute/client_library/snippets/tests/test_create_vm.py b/compute/client_library/snippets/tests/test_create_vm.py new file mode 100644 index 00000000000..5a2235efe0d --- /dev/null +++ b/compute/client_library/snippets/tests/test_create_vm.py @@ -0,0 +1,256 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import uuid + +import google.auth +from google.cloud import compute_v1 +import pytest + +from ..disks.create_empty_disk import create_empty_disk +from ..disks.create_from_image import create_disk_from_image +from ..disks.delete import delete_disk + +from ..instances.create_start_instance.create_from_custom_image import ( + create_from_custom_image, +) +from ..instances.create_start_instance.create_from_public_image import ( + create_from_public_image, +) +from ..instances.create_start_instance.create_from_snapshot import create_from_snapshot +from ..instances.create_start_instance.create_with_additional_disk import ( + create_with_additional_disk, +) +from ..instances.create_start_instance.create_with_existing_disks import create_with_existing_disks +from ..instances.create_start_instance.create_with_local_ssd import create_with_ssd +from ..instances.create_start_instance.create_with_snapshotted_data_disk import ( + create_with_snapshotted_data_disk, +) +from ..instances.create_with_subnet import create_with_subnet +from ..instances.delete import delete_instance +from ..operations.operation_check import wait_for_operation + +PROJECT = google.auth.default()[1] +REGION = "us-central1" +INSTANCE_ZONE = "us-central1-b" + + +def get_active_debian(): + image_client = compute_v1.ImagesClient() + + return image_client.get_from_family(project="debian-cloud", family="debian-11") + + +@pytest.fixture() +def src_disk(): + disk_client = compute_v1.DisksClient() + + disk = compute_v1.Disk() + disk.source_image = get_active_debian().self_link + disk.name = "test-disk-" + uuid.uuid4().hex[:10] + op = disk_client.insert_unary( + project=PROJECT, zone=INSTANCE_ZONE, disk_resource=disk + ) + + wait_for_operation(op, PROJECT) + try: + disk = disk_client.get(project=PROJECT, zone=INSTANCE_ZONE, disk=disk.name) + yield disk + finally: + op = disk_client.delete_unary( + project=PROJECT, zone=INSTANCE_ZONE, disk=disk.name + ) + wait_for_operation(op, PROJECT) + + +@pytest.fixture() +def snapshot(src_disk): + snapshot_client = compute_v1.SnapshotsClient() + snapshot = compute_v1.Snapshot() + snapshot.name = "test-snap-" + uuid.uuid4().hex[:10] + disk_client = compute_v1.DisksClient() + op = disk_client.create_snapshot_unary( + project=PROJECT, + zone=INSTANCE_ZONE, + disk=src_disk.name, + snapshot_resource=snapshot, + ) + wait_for_operation(op, PROJECT) + try: + snapshot = snapshot_client.get( + project=PROJECT, snapshot=snapshot.name + ) + + yield snapshot + finally: + op = snapshot_client.delete_unary(project=PROJECT, snapshot=snapshot.name) + wait_for_operation(op, PROJECT) + + +@pytest.fixture() +def image(src_disk): + image_client = compute_v1.ImagesClient() + image = compute_v1.Image() + image.source_disk = src_disk.self_link + image.name = "test-image-" + uuid.uuid4().hex[:10] + op = image_client.insert_unary(project=PROJECT, image_resource=image) + + wait_for_operation(op, PROJECT) + try: + image = image_client.get(project=PROJECT, image=image.name) + yield image + finally: + op = image_client.delete_unary(project=PROJECT, image=image.name) + wait_for_operation(op, PROJECT) + + +@pytest.fixture() +def boot_disk(): + debian_image = get_active_debian() + disk_name = "test-disk-" + uuid.uuid4().hex[:10] + disk = create_disk_from_image(PROJECT, INSTANCE_ZONE, disk_name, + f"zones/{INSTANCE_ZONE}/diskTypes/pd-standard", + 13, debian_image.self_link) + yield disk + delete_disk(PROJECT, INSTANCE_ZONE, disk_name) + + +@pytest.fixture() +def empty_disk(): + disk_name = "test-disk-" + uuid.uuid4().hex[:10] + disk = create_empty_disk(PROJECT, INSTANCE_ZONE, disk_name, + f"zones/{INSTANCE_ZONE}/diskTypes/pd-standard", + 14) + + yield disk + delete_disk(PROJECT, INSTANCE_ZONE, disk_name) + + +def test_create_from_custom_image(image): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_from_custom_image( + PROJECT, INSTANCE_ZONE, instance_name, image.self_link + ) + try: + assert ( + instance.disks[0].disk_size_gb == 10 + ) + finally: + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_create_from_public_image(): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_from_public_image( + PROJECT, + INSTANCE_ZONE, + instance_name, + ) + try: + assert instance.disks[0].disk_size_gb == 10 + finally: + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_create_from_snapshot(snapshot): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_from_snapshot( + PROJECT, INSTANCE_ZONE, instance_name, snapshot.self_link + ) + try: + assert ( + instance.disks[0].disk_size_gb == 20 + ) + finally: + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_create_with_additional_disk(): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_with_additional_disk(PROJECT, INSTANCE_ZONE, instance_name) + try: + assert any( + disk.disk_size_gb == 20 for disk in instance.disks + ) + assert any( + disk.disk_size_gb == 25 for disk in instance.disks + ) + assert len(instance.disks) == 2 + finally: + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_create_with_snapshotted_data_disk(snapshot): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_with_snapshotted_data_disk( + PROJECT, INSTANCE_ZONE, instance_name, snapshot.self_link + ) + try: + assert any( + disk.disk_size_gb == 11 for disk in instance.disks + ) + assert any( + disk.disk_size_gb == 10 for disk in instance.disks + ) + assert len(instance.disks) == 2 + finally: + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_create_with_subnet(): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_with_subnet( + PROJECT, + INSTANCE_ZONE, + instance_name, + "global/networks/default", + f"regions/{REGION}/subnetworks/default", + ) + try: + assert instance.network_interfaces[0].network.endswith("global/networks/default") + assert ( + instance.network_interfaces[0].subnetwork.endswith(f"regions/{REGION}/subnetworks/default") + ) + finally: + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_create_with_existing_disks(boot_disk, empty_disk): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_with_existing_disks(PROJECT, INSTANCE_ZONE, instance_name, + [boot_disk.name, empty_disk.name]) + + try: + assert any( + disk.disk_size_gb == 13 for disk in instance.disks + ) + assert any( + disk.disk_size_gb == 14 for disk in instance.disks + ) + assert len(instance.disks) == 2 + finally: + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_create_with_ssd(): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_with_ssd(PROJECT, INSTANCE_ZONE, instance_name) + + try: + assert any( + disk.type_ == compute_v1.AttachedDisk.Type.SCRATCH.name + for disk in instance.disks + ) + assert len(instance.disks) == 2 + finally: + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) diff --git a/compute/client_library/snippets/tests/test_custom_hostnames.py b/compute/client_library/snippets/tests/test_custom_hostnames.py new file mode 100644 index 00000000000..b8583db39bb --- /dev/null +++ b/compute/client_library/snippets/tests/test_custom_hostnames.py @@ -0,0 +1,51 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import random +import uuid + +import google.auth +import pytest + +from ..instances.custom_hostname.create import create_instance_custom_hostname +from ..instances.custom_hostname.get import get_hostname +from ..instances.delete import delete_instance + +PROJECT = google.auth.default()[1] +INSTANCE_ZONE = "europe-north1-c" + + +@pytest.fixture +def autodelete_instance_name(): + instance_name = "test-host-instance-" + uuid.uuid4().hex[:10] + + yield instance_name + + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +@pytest.fixture +def random_hostname(): + yield "instance.{}.hostname".format(random.randint(0, 2 ** 10)) + + +def test_custom_hostname(autodelete_instance_name, random_hostname): + instance = create_instance_custom_hostname( + PROJECT, INSTANCE_ZONE, autodelete_instance_name, random_hostname + ) + assert instance.name == autodelete_instance_name + assert instance.hostname == random_hostname + assert ( + get_hostname(PROJECT, INSTANCE_ZONE, autodelete_instance_name) + == random_hostname + ) diff --git a/compute/client_library/snippets/tests/test_custom_types.py b/compute/client_library/snippets/tests/test_custom_types.py new file mode 100644 index 00000000000..4b7c8108cb2 --- /dev/null +++ b/compute/client_library/snippets/tests/test_custom_types.py @@ -0,0 +1,212 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import uuid + +import google.auth +import pytest + +from ..images.get import get_image_from_family +from ..instances.create import create_instance +from ..instances.create_start_instance.create_from_public_image import disk_from_image +from ..instances.custom_machine_types.create_shared_with_helper import ( + create_custom_shared_core_instance, +) +from ..instances.custom_machine_types.create_with_helper import create_custom_instance +from ..instances.custom_machine_types.helper_class import CustomMachineType +from ..instances.custom_machine_types.update_memory import ( + add_extended_memory_to_instance, +) +from ..instances.delete import delete_instance + +PROJECT = google.auth.default()[1] +REGION = "us-central1" +INSTANCE_ZONE = "us-central1-b" + + +@pytest.fixture +def auto_delete_instance_name(): + instance_name = "test-instance-" + uuid.uuid4().hex[:10] + yield instance_name + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +@pytest.fixture +def instance(): + instance_name = "test-instance-" + uuid.uuid4().hex[:10] + + newest_debian = get_image_from_family(project="debian-cloud", family="debian-10") + disk_type = f"zones/{INSTANCE_ZONE}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)] + + instance = create_instance( + PROJECT, INSTANCE_ZONE, instance_name, disks, "n2-custom-8-10240" + ) + yield instance + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_custom_instance_creation(auto_delete_instance_name): + # Need to import CustomMachineType from this module, or the assertion will fail + from ..instances.custom_machine_types.create_with_helper import CustomMachineType + + instance = create_custom_instance( + PROJECT, + INSTANCE_ZONE, + auto_delete_instance_name, + CustomMachineType.CPUSeries.E2, + 4, + 8192, + ) + + assert instance.name == auto_delete_instance_name + assert instance.machine_type.endswith( + f"zones/{INSTANCE_ZONE}/machineTypes/e2-custom-4-8192" + ) + + +def test_custom_shared_instance_creation(auto_delete_instance_name): + # Need to import CustomMachineType from this module, or the assertion will fail + from ..instances.custom_machine_types.create_shared_with_helper import ( + CustomMachineType, + ) + + instance = create_custom_shared_core_instance( + PROJECT, + INSTANCE_ZONE, + auto_delete_instance_name, + CustomMachineType.CPUSeries.E2_MICRO, + 2048, + ) + + assert instance.name == auto_delete_instance_name + assert instance.machine_type.endswith( + f"zones/{INSTANCE_ZONE}/machineTypes/e2-custom-micro-2048" + ) + + +def test_custom_machine_type_good(): + # N1 + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.N1, 8192, 8) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/custom-8-8192" + assert cmt.short_type_str() == "custom-8-8192" + # N2 + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.N2, 4096, 4) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/n2-custom-4-4096" + assert cmt.short_type_str() == "n2-custom-4-4096" + # N2D + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.N2D, 8192, 4) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/n2d-custom-4-8192" + assert cmt.short_type_str() == "n2d-custom-4-8192" + # E2 + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.E2, 8192, 8) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/e2-custom-8-8192" + assert cmt.short_type_str() == "e2-custom-8-8192" + # E2 SMALL + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.E2_SMALL, 4096) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/e2-custom-small-4096" + assert cmt.short_type_str() == "e2-custom-small-4096" + # E2 MICRO + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.E2_MICRO, 2048) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/e2-custom-micro-2048" + assert cmt.short_type_str() == "e2-custom-micro-2048" + # E2 MEDIUM + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.E2_MEDIUM, 8192) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/e2-custom-medium-8192" + assert cmt.short_type_str() == "e2-custom-medium-8192" + + +def test_custom_machine_type_bad_memory_256(): + try: + CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.N1, 8194, 8) + except RuntimeError as err: + assert err.args[0] == "Requested memory must be a multiple of 256 MB." + else: + assert not "This test should have raised an exception!" + + +def test_custom_machine_type_ext_memory(): + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.N2, 638720, 8) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/n2-custom-8-638720-ext" + + +def test_custom_machine_type_bad_cpu_count(): + try: + CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.N2, 8194, 66) + except RuntimeError as err: + assert err.args[0].startswith( + "Invalid number of cores requested. Allowed number of cores for" + ) + else: + assert not "This test should have raised an exception!" + + +def test_add_extended_memory_to_instance(instance): + instance = add_extended_memory_to_instance( + PROJECT, INSTANCE_ZONE, instance.name, 819200 + ) + assert instance.machine_type.endswith("819200-ext") + + +def test_from_str_creation(): + cmt = CustomMachineType.from_str( + "https://www.googleapis.com/compute/v1/projects/diregapic-mestiv/zones/us-central1-b/machineTypes/e2-custom-4-8192" + ) + assert cmt.zone == "us-central1-b" + assert cmt.memory_mb == 8192 + assert cmt.extra_memory_used is False + assert cmt.cpu_series is CustomMachineType.CPUSeries.E2 + assert cmt.core_count == 4 + + cmt = CustomMachineType.from_str( + "zones/europe-west4-b/machineTypes/n2-custom-8-81920-ext" + ) + assert cmt.zone == "europe-west4-b" + assert cmt.memory_mb == 81920 + assert cmt.extra_memory_used is True + assert cmt.cpu_series is CustomMachineType.CPUSeries.N2 + assert cmt.core_count == 8 + + cmt = CustomMachineType.from_str( + "zones/europe-west4-b/machineTypes/e2-custom-small-4096" + ) + assert cmt.zone == "europe-west4-b" + assert cmt.memory_mb == 4096 + assert cmt.extra_memory_used is False + assert cmt.cpu_series == CustomMachineType.CPUSeries.E2_SMALL + assert cmt.core_count == 2 + + cmt = CustomMachineType.from_str( + "zones/europe-central2-b/machineTypes/custom-2-2048" + ) + assert cmt.zone == "europe-central2-b" + assert cmt.memory_mb == 2048 + assert cmt.extra_memory_used is False + assert cmt.cpu_series is CustomMachineType.CPUSeries.N1 + assert cmt.core_count == 2 + + try: + CustomMachineType.from_str( + "zones/europe-central2-b/machineTypes/n8-custom-2-1024" + ) + except RuntimeError as err: + assert err.args[0] == "Unknown CPU series." + else: + assert not "This was supposed to raise a RuntimeError." + + cmt = CustomMachineType.from_str("n2d-custom-8-81920-ext") + assert cmt.zone is None + assert cmt.memory_mb == 81920 + assert cmt.extra_memory_used is True + assert cmt.cpu_series is CustomMachineType.CPUSeries.N2D + assert cmt.core_count == 8 diff --git a/compute/client_library/snippets/tests/test_default_values.py b/compute/client_library/snippets/tests/test_default_values.py new file mode 100644 index 00000000000..f609b3dd522 --- /dev/null +++ b/compute/client_library/snippets/tests/test_default_values.py @@ -0,0 +1,74 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import time +import typing +import uuid + +from flaky import flaky +import google.auth +import google.cloud.storage as storage +import pytest + +from ..usage_report.usage_reports import disable_usage_export +from ..usage_report.usage_reports import get_usage_export_bucket +from ..usage_report.usage_reports import set_usage_export_bucket + +PROJECT = google.auth.default()[1] +BUCKET_NAME = "test" + uuid.uuid4().hex[:10] +TEST_PREFIX = "some-prefix" + + +@pytest.fixture +def temp_bucket(): + storage_client = storage.Client() + bucket = storage_client.create_bucket(BUCKET_NAME) + yield bucket + bucket.delete(force=True) + + +@flaky(max_runs=3) +def test_set_usage_export_bucket_default( + capsys: typing.Any, temp_bucket: storage.Bucket +) -> None: + set_usage_export_bucket(project_id=PROJECT, bucket_name=temp_bucket.name) + time.sleep(5) # To make sure the settings are properly updated + uel = get_usage_export_bucket(project_id=PROJECT) + assert uel.bucket_name == temp_bucket.name + assert uel.report_name_prefix == "usage_gce" + out, _ = capsys.readouterr() + assert "default prefix of `usage_gce`." in out + + disable_usage_export(project_id=PROJECT) + time.sleep(5) # To make sure the settings are properly updated + uel = get_usage_export_bucket(project_id=PROJECT) + assert uel.bucket_name == "" + assert uel.report_name_prefix == "" + + # Testing setting a custom export bucket. Keeping this in one test function + # to avoid race conditions, as this is a global setting for the project. + set_usage_export_bucket( + project_id=PROJECT, bucket_name=temp_bucket.name, report_name_prefix=TEST_PREFIX + ) + time.sleep(5) # To make sure the settings are properly updated + uel = get_usage_export_bucket(project_id=PROJECT) + assert uel.bucket_name == temp_bucket.name + assert uel.report_name_prefix == TEST_PREFIX + out, _ = capsys.readouterr() + assert "usage_gce" not in out + + disable_usage_export(project_id=PROJECT) + time.sleep(5) # To make sure the settings are properly updated + uel = get_usage_export_bucket(project_id=PROJECT) + assert uel.bucket_name == "" + assert uel.report_name_prefix == "" diff --git a/compute/client_library/snippets/tests/test_delete_protection.py b/compute/client_library/snippets/tests/test_delete_protection.py new file mode 100644 index 00000000000..643c9294d15 --- /dev/null +++ b/compute/client_library/snippets/tests/test_delete_protection.py @@ -0,0 +1,54 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import uuid + +import google.auth +import pytest + +from ..instances.delete import delete_instance +from ..instances.delete_protection.create import create_protected_instance +from ..instances.delete_protection.get import get_delete_protection +from ..instances.delete_protection.set import set_delete_protection + +PROJECT = google.auth.default()[1] +INSTANCE_ZONE = "europe-central2-a" + + +@pytest.fixture +def autodelete_instance_name(): + instance_name = "test-instance-" + uuid.uuid4().hex[:10] + + yield instance_name + + if get_delete_protection(PROJECT, INSTANCE_ZONE, instance_name): + set_delete_protection(PROJECT, INSTANCE_ZONE, instance_name, False) + + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_delete_protection(autodelete_instance_name): + instance = create_protected_instance( + PROJECT, INSTANCE_ZONE, autodelete_instance_name + ) + assert instance.name == autodelete_instance_name + + assert ( + get_delete_protection(PROJECT, INSTANCE_ZONE, autodelete_instance_name) is True + ) + + set_delete_protection(PROJECT, INSTANCE_ZONE, autodelete_instance_name, False) + + assert ( + get_delete_protection(PROJECT, INSTANCE_ZONE, autodelete_instance_name) is False + ) diff --git a/compute/client_library/snippets/tests/test_disks.py b/compute/client_library/snippets/tests/test_disks.py new file mode 100644 index 00000000000..75fa2e9867b --- /dev/null +++ b/compute/client_library/snippets/tests/test_disks.py @@ -0,0 +1,175 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import uuid + +from google.api_core.exceptions import NotFound +import google.auth +from google.cloud import kms_v1 +import pytest + +from ..disks.clone_encrypted_disk_managed_key import \ + create_disk_from_kms_encrypted_disk +from ..disks.create_from_image import create_disk_from_image +from ..disks.create_from_source import create_disk_from_disk +from ..disks.create_kms_encrypted_disk import create_kms_encrypted_disk +from ..disks.delete import delete_disk +from ..disks.list import list_disks +from ..disks.regional_create_from_source import create_regional_disk +from ..disks.regional_delete import delete_regional_disk +from ..images.get import get_image_from_family +from ..snapshots.create import create_snapshot +from ..snapshots.delete import delete_snapshot + +PROJECT = google.auth.default()[1] +ZONE = 'europe-north1-c' +REGION = 'europe-central2' +KMS_KEYRING_NAME = 'compute-test-keyring' +KMS_KEY_NAME = 'compute-test-key' + + +@pytest.fixture() +def kms_key(): + client = kms_v1.KeyManagementServiceClient() + location = f"projects/{PROJECT}/locations/global" + keyring_link = f"projects/{PROJECT}/locations/global/keyRings/{KMS_KEYRING_NAME}" + key_name = f"{keyring_link}/cryptoKeys/{KMS_KEY_NAME}" + + for ring in client.list_key_rings(parent=location): + if ring.name == keyring_link: + break + else: + client.create_key_ring(parent=location, key_ring_id=KMS_KEYRING_NAME) + + for key in client.list_crypto_keys(parent=keyring_link): + if key.name == key_name: + break + else: + key = kms_v1.CryptoKey() + key.purpose = key.CryptoKeyPurpose.ENCRYPT_DECRYPT + client.create_crypto_key(parent=keyring_link, crypto_key_id=KMS_KEY_NAME, crypto_key=key) + + yield client.get_crypto_key(name=key_name) + + +@pytest.fixture +def test_disk(): + """ + Get the newest version of debian 11 and make a disk from it. + """ + new_debian = get_image_from_family('debian-cloud', 'debian-11') + test_disk_name = "test-disk-" + uuid.uuid4().hex[:10] + disk = create_disk_from_image(PROJECT, ZONE, test_disk_name, + f"zones/{ZONE}/diskTypes/pd-standard", + 20, new_debian.self_link) + yield disk + delete_disk(PROJECT, ZONE, test_disk_name) + + +@pytest.fixture +def test_snapshot(test_disk): + """ + Make a snapshot that will be deleted when tests are done. + """ + test_snap_name = "test-snap-" + uuid.uuid4().hex[:10] + snap = create_snapshot(PROJECT, test_disk.name, test_snap_name, zone=test_disk.zone.rsplit('/')[-1]) + yield snap + delete_snapshot(PROJECT, snap.name) + + +@pytest.fixture() +def autodelete_disk_name(): + disk_name = "test-disk-" + uuid.uuid4().hex[:10] + yield disk_name + try: + delete_disk(PROJECT, ZONE, disk_name) + except NotFound: + # The disk was already deleted + pass + + +# To use the fixture 2 times in one test: +# https://stackoverflow.com/questions/36100624/pytest-use-same-fixture-twice-in-one-function +autodelete_disk_name2 = autodelete_disk_name + + +@pytest.fixture() +def autodelete_src_disk(autodelete_disk_name): + disk_type = f"zones/{ZONE}/diskTypes/pd-standard" + debian_image = get_image_from_family('debian-cloud', 'debian-11') + disk = create_disk_from_image(PROJECT, ZONE, autodelete_disk_name, disk_type, 24, debian_image.self_link) + yield disk + + +def test_disk_create_delete(autodelete_disk_name): + disk_type = f"zones/{ZONE}/diskTypes/pd-standard" + debian_image = get_image_from_family('debian-cloud', 'debian-11') + + disk = create_disk_from_image(PROJECT, ZONE, autodelete_disk_name, disk_type, 17, debian_image.self_link) + assert disk.name == autodelete_disk_name + assert disk.type_.endswith(disk_type) + assert disk.size_gb == 17 + + for i_disk in list_disks(PROJECT, ZONE): + if i_disk.name == autodelete_disk_name: + break + else: + pytest.fail("Couldn't find newly created disk on the disk list.") + + delete_disk(PROJECT, ZONE, autodelete_disk_name) + + for i_disk in list_disks(PROJECT, ZONE): + if i_disk.name == autodelete_disk_name: + pytest.fail("Found a disk that should be deleted on the disk list.") + + +def test_create_and_clone_encrypted_disk(autodelete_disk_name, kms_key, autodelete_disk_name2): + # The service account service-{PROJECT_ID}@compute-system.iam.gserviceaccount.com needs to have the + # cloudkms.cryptoKeyVersions.useToEncrypt permission to execute this test. + disk_type = f"zones/{ZONE}/diskTypes/pd-standard" + debian_image = get_image_from_family('debian-cloud', 'debian-11') + + disk = create_kms_encrypted_disk(PROJECT, ZONE, autodelete_disk_name, disk_type, 25, kms_key.name, + image_link=debian_image.self_link) + assert disk.name == autodelete_disk_name + assert disk.type_.endswith(disk_type) + + disk2 = create_disk_from_kms_encrypted_disk(PROJECT, ZONE, autodelete_disk_name2, disk_type, + 25, disk_link=disk.self_link, kms_key_name=kms_key.name) + assert disk2.name == autodelete_disk_name2 + assert disk2.type_.endswith(disk_type) + + +def test_create_disk_from_disk(autodelete_src_disk, autodelete_disk_name2): + disk_type = f"zones/{ZONE}/diskTypes/pd-standard" + new_disk = create_disk_from_disk(PROJECT, ZONE, autodelete_disk_name2, disk_type, 24, autodelete_src_disk.self_link) + + assert new_disk.type_.endswith(disk_type) + assert new_disk.name == autodelete_disk_name2 + + +def test_create_and_delete_regional_disk(test_snapshot): + disk_name = "test-rdisk-" + uuid.uuid4().hex[:10] + disk_type = f"regions/{REGION}/diskTypes/pd-balanced" + replica_zones = [ + f"projects/diregapic-mestiv/zones/{REGION}-a", + f"projects/diregapic-mestiv/zones/{REGION}-b", + ] + + try: + regional_disk = create_regional_disk(PROJECT, REGION, replica_zones, disk_name, + disk_type, 25, snapshot_link=test_snapshot.self_link) + assert regional_disk.name == disk_name + assert regional_disk.type_.endswith(disk_type) + finally: + delete_regional_disk(PROJECT, REGION, disk_name) diff --git a/compute/client_library/snippets/tests/test_firewall.py b/compute/client_library/snippets/tests/test_firewall.py new file mode 100644 index 00000000000..86f97867585 --- /dev/null +++ b/compute/client_library/snippets/tests/test_firewall.py @@ -0,0 +1,94 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import time +import uuid + +import google.api_core.exceptions +import google.auth +from google.cloud import compute_v1 +import pytest + +from ..firewall.create import create_firewall_rule +from ..firewall.delete import delete_firewall_rule +from ..firewall.main import get_firewall_rule +from ..firewall.patch import patch_firewall_priority + +PROJECT = google.auth.default()[1] + + +@pytest.fixture +def firewall_rule(): + firewall_rule = compute_v1.Firewall() + firewall_rule.name = "firewall-sample-test" + uuid.uuid4().hex[:10] + firewall_rule.direction = "INGRESS" + allowed_ports = compute_v1.Allowed() + allowed_ports.I_p_protocol = "tcp" + allowed_ports.ports = ["80"] + firewall_rule.allowed = [allowed_ports] + firewall_rule.source_ranges = ["0.0.0.0/0"] + firewall_rule.network = "global/networks/default" + firewall_rule.description = "Rule generated by Python sample test fixture." + firewall_rule.target_tags = ["web"] + + firewall_client = compute_v1.FirewallsClient() + op = firewall_client.insert_unary(project=PROJECT, firewall_resource=firewall_rule) + + op_client = compute_v1.GlobalOperationsClient() + op_client.wait(project=PROJECT, operation=op.name) + + yield firewall_client.get(project=PROJECT, firewall=firewall_rule.name) + + try: + op = firewall_client.delete_unary(project=PROJECT, firewall=firewall_rule.name) + op_client.wait(project=PROJECT, operation=op.name) + except google.api_core.exceptions.BadRequest as err: + if err.code == 400 and "is not ready" in err.message: + # This means GCE enforcer has already deleted that rule. + pass + else: + raise err + + +@pytest.fixture +def autodelete_firewall_name(): + """ + Provide a name for a firewall rule and then delete the rule. + """ + rule_name = "firewall-sample-test-" + uuid.uuid4().hex[:10] + yield rule_name + try: + delete_firewall_rule(PROJECT, rule_name) + except google.api_core.exceptions.BadRequest as err: + if err.code == 400 and "is not ready" in err.message: + # We can ignore this, this is most likely GCE Enforcer removing the rule before us. + pass + else: + # Something else went wrong, let's escalate it. + raise err + + +def test_create(autodelete_firewall_name): + create_firewall_rule(PROJECT, autodelete_firewall_name) + rule = get_firewall_rule(PROJECT, autodelete_firewall_name) + assert rule.name == autodelete_firewall_name + assert "web" in rule.target_tags + + +def test_patch_rule(firewall_rule): + fw_client = compute_v1.FirewallsClient() + assert firewall_rule.priority == 1000 + patch_firewall_priority(PROJECT, firewall_rule.name, 500) + time.sleep(2) + updated_firewall_rule = fw_client.get(project=PROJECT, firewall=firewall_rule.name) + assert updated_firewall_rule.priority == 500 diff --git a/compute/client_library/snippets/tests/test_images.py b/compute/client_library/snippets/tests/test_images.py new file mode 100644 index 00000000000..1cc7e6bb58f --- /dev/null +++ b/compute/client_library/snippets/tests/test_images.py @@ -0,0 +1,143 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import uuid + +import google.auth +from google.cloud import compute_v1 +import pytest + +from ..disks.create_from_image import create_disk_from_image +from ..disks.delete import delete_disk +from ..images.create import create_image_from_disk +from ..images.create_from_image import create_image_from_image +from ..images.create_from_snapshot import create_image_from_snapshot +from ..images.delete import delete_image +from ..images.get import get_image +from ..images.get import get_image_from_family +from ..images.list import list_images +from ..images.set_deprecation_status import set_deprecation_status +from ..snapshots.create import create_snapshot +from ..snapshots.delete import delete_snapshot + +PROJECT = google.auth.default()[1] +ZONE = 'europe-central2-c' + + +@pytest.fixture +def test_disk(): + """ + Get the newest version of debian 11 and make a disk from it. + """ + new_debian = get_image_from_family('debian-cloud', 'debian-11') + test_disk_name = "test-disk-" + uuid.uuid4().hex[:10] + disk = create_disk_from_image(PROJECT, ZONE, test_disk_name, + f"zones/{ZONE}/diskTypes/pd-standard", + 20, new_debian.self_link) + yield disk + delete_disk(PROJECT, ZONE, test_disk_name) + + +@pytest.fixture +def test_snapshot(test_disk): + """ + Make a snapshot that will be deleted when tests are done. + """ + test_snap_name = "test-snap-" + uuid.uuid4().hex[:10] + snap = create_snapshot(PROJECT, test_disk.name, test_snap_name, zone=test_disk.zone.rsplit('/')[-1]) + yield snap + delete_snapshot(PROJECT, snap.name) + + +@pytest.fixture +def autodelete_image_name(): + """ + Provide a name for an image that will be deleted after the test is done. + """ + test_img_name = "test-img-" + uuid.uuid4().hex[:10] + yield test_img_name + + delete_image(PROJECT, test_img_name) + + +@pytest.fixture() +def autodelete_image(autodelete_image_name): + """ + An image that will be deleted after the test is done. + """ + src_img = get_image_from_family('debian-cloud', 'debian-11') + new_image = create_image_from_image(PROJECT, src_img.name, autodelete_image_name, 'debian-cloud', + storage_location='eu') + yield new_image + + +def test_list_images(): + images = list_images("debian-cloud") + for img in images: + assert img.kind == "compute#image" + assert img.self_link.startswith( + "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/" + ) + + +def test_get_image(): + images = list_images("debian-cloud") + image = next(iter(images)) + + image2 = get_image("debian-cloud", image.name) + + assert image.name == image2.name + + +def test_create_delete_image(test_disk): + test_image_name = "test-image-" + uuid.uuid4().hex[:10] + new_image = create_image_from_disk(PROJECT, ZONE, test_disk.name, test_image_name) + try: + assert new_image.name == test_image_name + assert new_image.disk_size_gb == 20 + assert isinstance(new_image, compute_v1.Image) + finally: + delete_image(PROJECT, test_image_name) + + for image in list_images(PROJECT): + if image.name == test_image_name: + pytest.fail(f"Image {test_image_name} should have been deleted.") + + +def test_image_from_image(autodelete_image_name): + src_img = get_image_from_family('ubuntu-os-cloud', 'ubuntu-2204-lts') + new_image = create_image_from_image(PROJECT, src_img.name, autodelete_image_name, 'ubuntu-os-cloud', + guest_os_features=[compute_v1.GuestOsFeature.Type.MULTI_IP_SUBNET.name], + storage_location='eu') + + assert new_image.storage_locations == ['eu'] + assert new_image.disk_size_gb == src_img.disk_size_gb + assert new_image.name == autodelete_image_name + assert any(feature.type_ == compute_v1.GuestOsFeature.Type.MULTI_IP_SUBNET.name for feature in new_image.guest_os_features) + + +def test_image_from_snapshot(test_snapshot, autodelete_image_name): + img = create_image_from_snapshot(PROJECT, test_snapshot.name, autodelete_image_name, + guest_os_features=[compute_v1.GuestOsFeature.Type.MULTI_IP_SUBNET.name], + storage_location='us-central1') + assert img.storage_locations == ['us-central1'] + assert img.name == autodelete_image_name + assert any( + feature.type_ == compute_v1.GuestOsFeature.Type.MULTI_IP_SUBNET.name for feature in img.guest_os_features) + + +def test_status_change(autodelete_image): + set_deprecation_status(PROJECT, autodelete_image.name, compute_v1.DeprecationStatus.State.DEPRECATED) + img = get_image(PROJECT, autodelete_image.name) + assert img.name == autodelete_image.name + assert img.deprecated.state == compute_v1.DeprecationStatus.State.DEPRECATED.name diff --git a/compute/client_library/snippets/tests/test_instance_from_template.py b/compute/client_library/snippets/tests/test_instance_from_template.py new file mode 100644 index 00000000000..e8a20f372d8 --- /dev/null +++ b/compute/client_library/snippets/tests/test_instance_from_template.py @@ -0,0 +1,104 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import uuid + +import google.auth +from google.cloud import compute_v1 +import pytest + +from ..instances.delete import delete_instance +from ..instances.from_instance_template.create_from_template import ( + create_instance_from_template, +) +from ..instances.from_instance_template.create_from_template_with_overrides import ( + create_instance_from_template_with_overrides, +) + +PROJECT = google.auth.default()[1] +INSTANCE_ZONE = "europe-north1-c" + + +@pytest.fixture +def instance_template(): + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = ( + "projects/debian-cloud/global/images/family/debian-11" + ) + initialize_params.disk_size_gb = 25 + initialize_params.disk_type = 'pd-balanced' + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + + network_interface = compute_v1.NetworkInterface() + network_interface.name = "global/networks/default" + + template = compute_v1.InstanceTemplate() + template.name = "test-template-" + uuid.uuid4().hex[:10] + template.properties.disks = [disk] + template.properties.machine_type = "n1-standard-4" + template.properties.network_interfaces = [network_interface] + + template_client = compute_v1.InstanceTemplatesClient() + operation_client = compute_v1.GlobalOperationsClient() + op = template_client.insert_unary( + project=PROJECT, instance_template_resource=template + ) + operation_client.wait(project=PROJECT, operation=op.name) + + template = template_client.get(project=PROJECT, instance_template=template.name) + + yield template + + op = template_client.delete_unary(project=PROJECT, instance_template=template.name) + operation_client.wait(project=PROJECT, operation=op.name) + + +@pytest.fixture() +def autodelete_instance_name(): + instance_name = "test-instance-" + uuid.uuid4().hex[:10] + yield instance_name + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_create_instance_from_template(instance_template, autodelete_instance_name): + instance = create_instance_from_template( + PROJECT, INSTANCE_ZONE, autodelete_instance_name, instance_template.self_link + ) + + assert instance.name == autodelete_instance_name + assert instance.zone.endswith(INSTANCE_ZONE) + + +def test_create_instance_from_template_override( + instance_template, autodelete_instance_name +): + image_client = compute_v1.ImagesClient() + + image = image_client.get_from_family( + project="ubuntu-os-cloud", family="ubuntu-2004-lts" + ) + instance = create_instance_from_template_with_overrides( + PROJECT, + INSTANCE_ZONE, + autodelete_instance_name, + instance_template.name, + f"zones/{INSTANCE_ZONE}/machineTypes/n2-standard-2", + image.self_link, + ) + + assert instance.name == autodelete_instance_name + assert instance.machine_type.endswith("n2-standard-2") + assert len(instance.disks) == 2 diff --git a/compute/client_library/snippets/tests/test_instance_start_stop.py b/compute/client_library/snippets/tests/test_instance_start_stop.py new file mode 100644 index 00000000000..8c6efc802de --- /dev/null +++ b/compute/client_library/snippets/tests/test_instance_start_stop.py @@ -0,0 +1,189 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import base64 +import random +import string +import time +import uuid + +import google.auth +from google.cloud import compute_v1 +import pytest + +from ..disks.clone_encrypted_disk import create_disk_from_customer_encrypted_disk +from ..disks.delete import delete_disk +from ..instances.start import start_instance +from ..instances.start_encrypted import start_instance_with_encryption_key +from ..instances.stop import stop_instance + +PROJECT = google.auth.default()[1] + +INSTANCE_ZONE = "europe-central2-b" + +KEY = "".join(random.sample(string.ascii_letters, 32)) +KEY_B64 = base64.b64encode( + KEY.encode() +) # for example: b'VEdORldtY3NKellPdWRDcUF5YlNVREtJdm5qaFJYSFA=' + + +def _make_disk(raw_key: bytes = None): + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = ( + "projects/debian-cloud/global/images/family/debian-10" + ) + initialize_params.disk_size_gb = 10 + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + disk.type_ = "PERSISTENT" + disk.device_name = "disk-1" + + if raw_key: + disk.disk_encryption_key = compute_v1.CustomerEncryptionKey() + disk.disk_encryption_key.raw_key = raw_key + + return disk + + +def _make_request(disk: compute_v1.AttachedDisk): + network_interface = compute_v1.NetworkInterface() + network_interface.name = "default" + network_interface.access_configs = [] + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.name = "i" + uuid.uuid4().hex[:10] + instance.disks = [disk] + full_machine_type_name = f"zones/{INSTANCE_ZONE}/machineTypes/e2-micro" + instance.machine_type = full_machine_type_name + instance.network_interfaces = [network_interface] + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = INSTANCE_ZONE + request.project = PROJECT + request.instance_resource = instance + return request + + +def _create_instance(request: compute_v1.InsertInstanceRequest): + instance_client = compute_v1.InstancesClient() + operation_client = compute_v1.ZoneOperationsClient() + + operation = instance_client.insert_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=INSTANCE_ZONE, project=PROJECT + ) + + return instance_client.get( + project=PROJECT, zone=INSTANCE_ZONE, instance=request.instance_resource.name + ) + + +def _delete_instance(instance: compute_v1.Instance): + instance_client = compute_v1.InstancesClient() + operation_client = compute_v1.ZoneOperationsClient() + + operation = instance_client.delete_unary( + project=PROJECT, zone=INSTANCE_ZONE, instance=instance.name + ) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=INSTANCE_ZONE, project=PROJECT + ) + + +def _get_status(instance: compute_v1.Instance) -> compute_v1.Instance.Status: + instance_client = compute_v1.InstancesClient() + return instance_client.get( + project=PROJECT, zone=INSTANCE_ZONE, instance=instance.name + ).status + + +@pytest.fixture +def compute_instance(): + disk = _make_disk() + request = _make_request(disk) + + instance = _create_instance(request) + + yield instance + + _delete_instance(instance) + + +@pytest.fixture +def compute_encrypted_instance(): + disk = _make_disk(KEY_B64) + request = _make_request(disk) + + instance = _create_instance(request) + + yield instance + + _delete_instance(instance) + + +@pytest.fixture +def autodelete_disk_name(): + name = "d" + uuid.uuid4().hex[:10] + yield name + delete_disk(PROJECT, INSTANCE_ZONE, name) + + +def test_instance_operations(compute_instance): + assert _get_status(compute_instance) == "RUNNING" + + stop_instance(PROJECT, INSTANCE_ZONE, compute_instance.name) + + while _get_status(compute_instance) == "STOPPING": + # Since we can't configure timeout parameter for operation wait() (b/188037306) + # We need to do some manual waiting for the stopping to finish... + time.sleep(5) + + assert _get_status(compute_instance) == "TERMINATED" + + start_instance(PROJECT, INSTANCE_ZONE, compute_instance.name) + assert _get_status(compute_instance) == "RUNNING" + + +def test_instance_encrypted(compute_encrypted_instance): + assert _get_status(compute_encrypted_instance) == "RUNNING" + + stop_instance(PROJECT, INSTANCE_ZONE, compute_encrypted_instance.name) + while _get_status(compute_encrypted_instance) == "STOPPING": + # Since we can't configure timeout parameter for operation wait() (b/188037306) + # We need to do some manual waiting for the stopping to finish... + time.sleep(5) + + assert _get_status(compute_encrypted_instance) == "TERMINATED" + + start_instance_with_encryption_key( + PROJECT, INSTANCE_ZONE, compute_encrypted_instance.name, KEY_B64 + ) + assert _get_status(compute_encrypted_instance) == "RUNNING" + + +def test_clone_encrypted_disk(autodelete_disk_name, compute_encrypted_instance): + assert _get_status(compute_encrypted_instance) == "RUNNING" + + new_disk = create_disk_from_customer_encrypted_disk( + PROJECT, INSTANCE_ZONE, autodelete_disk_name, + f"zones/{INSTANCE_ZONE}/diskTypes/pd-standard", + 10, compute_encrypted_instance.disks[0].source, + encryption_key=KEY_B64) + + assert new_disk.name == autodelete_disk_name diff --git a/compute/client_library/snippets/tests/test_instance_suspend_resume.py b/compute/client_library/snippets/tests/test_instance_suspend_resume.py new file mode 100644 index 00000000000..a6f2ca103f0 --- /dev/null +++ b/compute/client_library/snippets/tests/test_instance_suspend_resume.py @@ -0,0 +1,69 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import time +import uuid + +import google.auth +from google.cloud import compute_v1 +import pytest + + +from ..images.get import get_image_from_family +from ..instances.create import create_instance, disk_from_image +from ..instances.delete import delete_instance +from ..instances.resume import resume_instance +from ..instances.suspend import suspend_instance + +PROJECT = google.auth.default()[1] + +INSTANCE_ZONE = "europe-central2-b" + + +def _get_status(instance: compute_v1.Instance) -> compute_v1.Instance.Status: + instance_client = compute_v1.InstancesClient() + return instance_client.get( + project=PROJECT, zone=INSTANCE_ZONE, instance=instance.name + ).status + + +@pytest.fixture +def compute_instance(): + instance_name = "test-instance-" + uuid.uuid4().hex[:10] + newest_debian = get_image_from_family(project="ubuntu-os-cloud", family="ubuntu-2004-lts") + disk_type = f"zones/{INSTANCE_ZONE}/diskTypes/pd-standard" + disks = [disk_from_image(disk_type, 100, True, newest_debian.self_link)] + instance = create_instance( + PROJECT, INSTANCE_ZONE, instance_name, disks + ) + yield instance + + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_instance_suspend_resume(compute_instance): + assert _get_status(compute_instance) == compute_v1.Instance.Status.RUNNING.name + + # Once the machine is running, give it some time to fully start all processes + # before trying to suspend it + time.sleep(45) + + suspend_instance(PROJECT, INSTANCE_ZONE, compute_instance.name) + + while _get_status(compute_instance) == compute_v1.Instance.Status.SUSPENDING.name: + time.sleep(5) + + assert _get_status(compute_instance) == compute_v1.Instance.Status.SUSPENDED.name + + resume_instance(PROJECT, INSTANCE_ZONE, compute_instance.name) + assert _get_status(compute_instance) == compute_v1.Instance.Status.RUNNING.name diff --git a/compute/client_library/snippets/tests/test_pagination.py b/compute/client_library/snippets/tests/test_pagination.py new file mode 100644 index 00000000000..41e06703d13 --- /dev/null +++ b/compute/client_library/snippets/tests/test_pagination.py @@ -0,0 +1,27 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from ..images.pagination import print_images_list +from ..images.pagination import print_images_list_by_page + +PROJECT = "windows-sql-cloud" + + +def test_pagination() -> None: + out = print_images_list(PROJECT) + assert len(out.splitlines()) > 2 + + +def test_pagination_page() -> None: + out = print_images_list_by_page(PROJECT, 2) + assert "Page 2" in out diff --git a/compute/client_library/snippets/tests/test_preemptible.py b/compute/client_library/snippets/tests/test_preemptible.py new file mode 100644 index 00000000000..06c5092799c --- /dev/null +++ b/compute/client_library/snippets/tests/test_preemptible.py @@ -0,0 +1,58 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import uuid + +import google.auth +import pytest + +from ..instances.delete import delete_instance +from ..instances.preemptible.create_preemptible import create_preemptible_instance +from ..instances.preemptible.is_preemptible import is_preemptible +from ..instances.preemptible.preemption_history import list_zone_operations + +PROJECT = google.auth.default()[1] +INSTANCE_ZONE = "europe-central2-c" + + +@pytest.fixture +def autodelete_instance_name(): + instance_name = "i" + uuid.uuid4().hex[:10] + + yield instance_name + + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_preemptible_creation(autodelete_instance_name): + instance = create_preemptible_instance( + PROJECT, INSTANCE_ZONE, autodelete_instance_name + ) + + assert instance.name == autodelete_instance_name + assert is_preemptible(PROJECT, INSTANCE_ZONE, instance.name) + + operations = list_zone_operations( + PROJECT, + INSTANCE_ZONE, + f'targetLink="https://www.googleapis.com/compute/v1/projects/' + f'{PROJECT}/zones/{INSTANCE_ZONE}/instances/{instance.name}"', + ) + + # Since ListPagers don't support len(), we need to check it manually + try: + next(iter(operations)) + except StopIteration: + pytest.fail( + "There should be at least one operation for this instance at this point." + ) diff --git a/compute/client_library/snippets/tests/test_request_id.py b/compute/client_library/snippets/tests/test_request_id.py new file mode 100644 index 00000000000..90dcc7e028c --- /dev/null +++ b/compute/client_library/snippets/tests/test_request_id.py @@ -0,0 +1,98 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from copy import deepcopy +import uuid + +import google.api_core.exceptions +import google.auth +from google.cloud import compute_v1 +import pytest + + +from ..disks.delete import delete_disk +from ..disks.list import list_disks +from ..instances.create_start_instance.create_windows_instance import \ + get_image_from_family + +PROJECT = google.auth.default()[1] +ZONE = "europe-north1-c" + + +def test_request_id(): + disk = compute_v1.Disk() + disk.size_gb = 20 + disk.name = "test-disk-" + uuid.uuid4().hex[:10] + disk.zone = ZONE + disk.type_ = f"zones/{ZONE}/diskTypes/pd-standard" + disk.source_image = get_image_from_family("debian-cloud", "debian-11").self_link + + disk2 = deepcopy(disk) + disk2.name = "test-disk-" + uuid.uuid4().hex[:10] + + request = compute_v1.InsertDiskRequest() + request.request_id = str(uuid.uuid4()) + request.project = PROJECT + request.zone = ZONE + request.disk_resource = disk + + # Creating a different request, but with the same request_id + # This should not be executed, because the previous request + # has the same ID. + request2 = deepcopy(request) + request2.disk_resource = disk2 + + disk_client = compute_v1.DisksClient() + try: + operation = disk_client.insert(request) + operation2 = disk_client.insert(request2) + operation.result() + operation2.result() + except Exception as err: + pytest.fail(f"There was an error: {err}") + raise err + else: + disks = list_disks(PROJECT, ZONE) + assert any(i_disk.name == disk.name for i_disk in disks) + assert all(i_disk.name != disk2.name for i_disk in disks) + finally: + delete_disk(PROJECT, ZONE, disk.name) + try: + delete_disk(PROJECT, ZONE, disk2.name) + except google.api_core.exceptions.NotFound: + pass + + +def test_request_id_op_id(): + disk = compute_v1.Disk() + disk.size_gb = 20 + disk.name = "test-disk-" + uuid.uuid4().hex[:10] + disk.zone = ZONE + disk.type_ = f"zones/{ZONE}/diskTypes/pd-standard" + disk.source_image = get_image_from_family("debian-cloud", "debian-11").self_link + + request = compute_v1.InsertDiskRequest() + request.request_id = str(uuid.uuid4()) + request.project = PROJECT + request.zone = ZONE + request.disk_resource = disk + + disk_client = compute_v1.DisksClient() + + try: + op1 = disk_client.insert(request) + op2 = disk_client.insert(request) + op1.result() + assert op1.name == op2.name + finally: + delete_disk(PROJECT, ZONE, disk.name) diff --git a/compute/client_library/snippets/tests/test_route.py b/compute/client_library/snippets/tests/test_route.py new file mode 100644 index 00000000000..65ccbf81ece --- /dev/null +++ b/compute/client_library/snippets/tests/test_route.py @@ -0,0 +1,38 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import uuid + +import google.auth +import pytest + +from ..routes.create_kms_route import create_route_to_windows_activation_host +from ..routes.delete import delete_route +from ..routes.list import list_routes + +PROJECT = google.auth.default()[1] + + +def test_route_create_delete(): + route_name = "test-route" + uuid.uuid4().hex[:10] + route = create_route_to_windows_activation_host(PROJECT, "global/networks/default", route_name) + try: + assert route.name == route_name + assert route.dest_range == "35.190.247.13/32" + finally: + + delete_route(PROJECT, route_name) + + for route in list_routes(PROJECT): + if route.name == route_name: + pytest.fail(f"Failed to delete test route {route_name}.") diff --git a/compute/client_library/snippets/tests/test_snapshots.py b/compute/client_library/snippets/tests/test_snapshots.py new file mode 100644 index 00000000000..b62978788e3 --- /dev/null +++ b/compute/client_library/snippets/tests/test_snapshots.py @@ -0,0 +1,64 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import uuid + +import google.auth +import pytest + +from ..disks.create_from_image import create_disk_from_image +from ..disks.delete import delete_disk +from ..images.get import get_image_from_family +from ..snapshots.create import create_snapshot +from ..snapshots.delete import delete_snapshot +from ..snapshots.get import get_snapshot +from ..snapshots.list import list_snapshots + +PROJECT = google.auth.default()[1] +ZONE = 'europe-north1-c' + + +@pytest.fixture +def test_disk(): + debian_image = get_image_from_family('debian-cloud', 'debian-11') + test_disk_name = "test-disk-" + uuid.uuid4().hex[:10] + + disk_type = f"zones/{ZONE}/diskTypes/pd-standard" + + disk = create_disk_from_image(PROJECT, ZONE, test_disk_name, disk_type, 20, debian_image.self_link) + + yield disk + + delete_disk(PROJECT, ZONE, test_disk_name) + + +def test_snapshot_create_delete(test_disk): + snapshot_name = "test-snapshot-" + uuid.uuid4().hex[:10] + snapshot = create_snapshot(PROJECT, test_disk.name, snapshot_name, zone=ZONE) + assert snapshot.name == snapshot_name + assert snapshot.source_disk == test_disk.self_link + for i_snapshot in list_snapshots(PROJECT): + if i_snapshot.name == snapshot_name: + break + else: + pytest.fail("Couldn't find the created snapshot on snapshot list.") + + snapshot_get = get_snapshot(PROJECT, snapshot_name) + assert snapshot_get.name == snapshot_name + assert snapshot_get.disk_size_gb == snapshot.disk_size_gb + assert snapshot_get.self_link == snapshot.self_link + + delete_snapshot(PROJECT, snapshot_name) + for i_snapshot in list_snapshots(PROJECT): + if i_snapshot.name == snapshot_name: + pytest.fail("Test snapshot found on snapshot list, while it should already be gone.") diff --git a/compute/client_library/snippets/tests/test_spot_vms.py b/compute/client_library/snippets/tests/test_spot_vms.py new file mode 100644 index 00000000000..5e6114161ca --- /dev/null +++ b/compute/client_library/snippets/tests/test_spot_vms.py @@ -0,0 +1,42 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import uuid + +import google.auth +import pytest + +from ..instances.delete import delete_instance +from ..instances.spot.create import create_spot_instance +from ..instances.spot.is_spot_vm import is_spot_vm + +PROJECT = google.auth.default()[1] +INSTANCE_ZONE = "europe-central2-c" + + +@pytest.fixture +def autodelete_instance_name(): + instance_name = "i" + uuid.uuid4().hex[:10] + + yield instance_name + + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_preemptible_creation(autodelete_instance_name): + instance = create_spot_instance( + PROJECT, INSTANCE_ZONE, autodelete_instance_name + ) + + assert instance.name == autodelete_instance_name + assert is_spot_vm(PROJECT, INSTANCE_ZONE, instance.name) diff --git a/compute/client_library/snippets/tests/test_templates.py b/compute/client_library/snippets/tests/test_templates.py new file mode 100644 index 00000000000..bf04fa12b8a --- /dev/null +++ b/compute/client_library/snippets/tests/test_templates.py @@ -0,0 +1,107 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import uuid + +import google.auth +import pytest + +# Turning off F401 check because flake8 doesn't recognize using +# PyTest fixture as parameter as usage. +from .test_instance_start_stop import compute_instance # noqa: F401 + +from ..instance_templates.create import create_template +from ..instance_templates.create_from_instance import \ + create_template_from_instance +from ..instance_templates.create_with_subnet import create_template_with_subnet +from ..instance_templates.delete import delete_instance_template +from ..instance_templates.list import list_instance_templates + +PROJECT = google.auth.default()[1] + +INSTANCE_ZONE = "europe-central2-b" + + +@pytest.fixture +def deletable_template_name(): + template_name = "i" + uuid.uuid4().hex[:10] + yield template_name + delete_instance_template(PROJECT, template_name) + + +@pytest.fixture +def template_to_be_deleted(): + template_name = "i" + uuid.uuid4().hex[:10] + template = create_template(PROJECT, template_name) + yield template + + +def test_create_template_and_list(deletable_template_name): + + template = create_template(PROJECT, deletable_template_name) + + assert template.name == deletable_template_name + assert any( + template.name == deletable_template_name + for template in list_instance_templates(PROJECT) + ) + assert template.properties.disks[0].initialize_params.disk_size_gb == 250 + assert "debian-11" in template.properties.disks[0].initialize_params.source_image + assert template.properties.network_interfaces[0].name == "global/networks/default" + assert template.properties.machine_type == "e2-standard-4" + + +def test_create_from_instance(compute_instance, deletable_template_name): # noqa: F811 + + template = create_template_from_instance( + PROJECT, compute_instance.self_link, deletable_template_name + ) + + assert template.name == deletable_template_name + assert template.properties.machine_type in compute_instance.machine_type + assert ( + template.properties.disks[0].disk_size_gb + == compute_instance.disks[0].disk_size_gb + ) + assert ( + template.properties.disks[0].initialize_params.source_image + == "projects/rocky-linux-cloud/global/images/family/rocky-linux-8" + ) + + +def test_create_template_with_subnet(deletable_template_name): + template = create_template_with_subnet( + PROJECT, + "global/networks/default", + "regions/asia-east1/subnetworks/default", + deletable_template_name, + ) + + assert template.name == deletable_template_name + assert ( + "global/networks/default" in template.properties.network_interfaces[0].network + ) + assert ( + "regions/asia-east1/subnetworks/default" + in template.properties.network_interfaces[0].subnetwork + ) + + +def test_delete_template(template_to_be_deleted): + delete_instance_template(PROJECT, template_to_be_deleted.name) + + assert all( + template.name != template_to_be_deleted.name + for template in list_instance_templates(PROJECT) + ) diff --git a/compute/client_library/snippets/usage_report/__init__.py b/compute/client_library/snippets/usage_report/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/client_library/snippets/usage_report/usage_reports.py b/compute/client_library/snippets/usage_report/usage_reports.py new file mode 100644 index 00000000000..e04a3a8a162 --- /dev/null +++ b/compute/client_library/snippets/usage_report/usage_reports.py @@ -0,0 +1,235 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa +""" +A sample script showing how to handle default values when communicating +with the Compute Engine API and how to configure usage reports using the API. +""" + +# This file is automatically generated. Please do not modify it directly. +# Find the relevant recipe file in the samples/recipes or samples/ingredients +# directory and apply your changes there. + + +# [START compute_instances_verify_default_value] +# [START compute_usage_report_set] +# [START compute_usage_report_get] +# [START compute_usage_report_disable] +import sys +from typing import Any + +from google.api_core.extended_operation import ExtendedOperation +from google.cloud import compute_v1 + + +# [END compute_usage_report_disable] +# [END compute_usage_report_get] +# [END compute_usage_report_set] + + +# [START compute_usage_report_set] + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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 set_usage_export_bucket( + project_id: str, bucket_name: str, report_name_prefix: str = "" +) -> None: + """ + Set Compute Engine usage export bucket for the Cloud project. + This sample presents how to interpret the default value for the + report name prefix parameter. + + Args: + project_id: project ID or project number of the project to update. + bucket_name: Google Cloud Storage bucket used to store Compute Engine + usage reports. An existing Google Cloud Storage bucket is required. + report_name_prefix: Prefix of the usage report name which defaults to an empty string + to showcase default values behaviour. + """ + usage_export_location = compute_v1.UsageExportLocation() + usage_export_location.bucket_name = bucket_name + usage_export_location.report_name_prefix = report_name_prefix + + if not report_name_prefix: + # Sending an empty value for report_name_prefix results in the + # next usage report being generated with the default prefix value + # "usage_gce". (ref: https://cloud.google.com/compute/docs/reference/rest/v1/projects/setUsageExportBucket) + print( + "Setting report_name_prefix to empty value causes the report " + "to have the default prefix of `usage_gce`." + ) + + projects_client = compute_v1.ProjectsClient() + operation = projects_client.set_usage_export_bucket( + project=project_id, usage_export_location_resource=usage_export_location + ) + + wait_for_extended_operation(operation, "setting GCE usage bucket") + + +# [END compute_usage_report_set] + +# [START compute_usage_report_get] +def get_usage_export_bucket(project_id: str) -> compute_v1.UsageExportLocation: + """ + Retrieve Compute Engine usage export bucket for the Cloud project. + Replaces the empty value returned by the API with the default value used + to generate report file names. + + Args: + project_id: project ID or project number of the project to update. + Returns: + UsageExportLocation object describing the current usage export settings + for project project_id. + """ + projects_client = compute_v1.ProjectsClient() + project_data = projects_client.get(project=project_id) + + uel = project_data.usage_export_location + + if not uel.bucket_name: + # The usage reports are disabled. + return uel + + if not uel.report_name_prefix: + # Although the server sent the empty string value, the next usage report + # generated with these settings still has the default prefix value + # "usage_gce". (see https://cloud.google.com/compute/docs/reference/rest/v1/projects/get) + print( + "Report name prefix not set, replacing with default value of " + "`usage_gce`." + ) + uel.report_name_prefix = "usage_gce" + return uel + + +# [END compute_usage_report_get] +# [END compute_instances_verify_default_value] + + +# [START compute_usage_report_disable] + + +def wait_for_extended_operation( + operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300 +) -> Any: + """ + This method will wait 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 disable_usage_export(project_id: str) -> None: + """ + Disable Compute Engine usage export bucket for the Cloud Project. + + Args: + project_id: project ID or project number of the project to update. + """ + projects_client = compute_v1.ProjectsClient() + + # Setting `usage_export_location_resource` to an + # empty object will disable the usage report generation. + operation = projects_client.set_usage_export_bucket( + project=project_id, usage_export_location_resource={} + ) + + wait_for_extended_operation(operation, "disabling GCE usage bucket") + + +# [END compute_usage_report_disable] diff --git a/compute/client_library/test_sgs.py b/compute/client_library/test_sgs.py new file mode 100644 index 00000000000..fafae9ed3c7 --- /dev/null +++ b/compute/client_library/test_sgs.py @@ -0,0 +1,57 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from argparse import Namespace +import glob +from pathlib import Path +import tempfile + +import pytest + +from . import sgs + +FIXTURE_INGREDIENTS = Path("sgs_test_fixtures/ingredients") +FIXTURE_RECIPES = Path("sgs_test_fixtures/recipes") +FIXTURE_OUTPUT = Path("sgs_test_fixtures/output") + + +def test_sgs_generate(): + with tempfile.TemporaryDirectory() as tmp_dir: + args = Namespace(output_dir=tmp_dir) + sgs.generate(args, FIXTURE_INGREDIENTS.absolute(), FIXTURE_RECIPES.absolute()) + for test_file in map(Path, glob.glob(f"{tmp_dir}/**")): + match_file = FIXTURE_OUTPUT / test_file.relative_to(tmp_dir) + assert test_file.read_bytes() == match_file.read_bytes() + + +def test_snippets_freshness(): + """ + Make sure that the snippets/ folder is up-to-date and matches + ingredients/ and recipes/. This test will generate SGS output + in a temporary directory and compare it to the content of + snippets/ folder. + """ + with tempfile.TemporaryDirectory() as tmp_dir: + args = Namespace(output_dir=tmp_dir) + sgs.generate(args, Path("ingredients/").absolute(), Path("recipes/").absolute()) + print(list(map(Path, glob.glob(f"{tmp_dir}/**")))) + for test_file in map(Path, glob.glob(f"{tmp_dir}/**", recursive=True)): + match_file = Path("snippets/") / test_file.relative_to(tmp_dir) + if test_file.is_file(): + if test_file.read_bytes() != match_file.read_bytes(): + pytest.fail( + f"This test fails because file {match_file} seems to be outdated. Please run " + f"`python sgs.py generate` to update your snippets." + ) + elif test_file.is_dir(): + assert match_file.is_dir()