From ca29697b66611eb9b8b7e1d649b4b0147d03a55e Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Sun, 6 Jun 2021 15:07:03 +0200 Subject: [PATCH 001/113] feat: Adding code samples and tests for them (#55) * feat: Adding code samples and tests for them * chore: Remove unused import. * Update .github/CODEOWNERS Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> * chore: Updating samples to meet reviewers suggestions. * chore: Fixing regions. * chore: Adjusting the testing scripts in .kokoro/samples. * Revert "chore: Adjusting the testing scripts in .kokoro/samples." This reverts commit 6d0b4276 * chore: Moving samples tests to dedicated noxfile.py. * chore: Adding 3.6 and 3.7 Python versions to samples noxfile.py * chore: Updating the Samples section to reflect new testing setup. * chore: Updating the Samples README. * chore: add standardized samples noxfile * chore: uncomment sections in synth.py * chore: Changing the waiting for operation part. * chore: Minor changes based on review. * README suggests using `gcloud auth application-default login` which is safer than Service Account key. * The name of created instance now starts with "quickstart-". * Changed one variable name. Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Co-authored-by: Bu Sun Kim Co-authored-by: Dina Graves Portman --- compute/compute/snippets/README.md | 31 ++ compute/compute/snippets/noxfile.py | 259 +++++++++++++++++ compute/compute/snippets/quickstart.py | 275 ++++++++++++++++++ .../compute/snippets/requirements-test.txt | 1 + compute/compute/snippets/requirements.txt | 1 + compute/compute/snippets/test_quickstart.py | 36 +++ 6 files changed, 603 insertions(+) create mode 100644 compute/compute/snippets/README.md create mode 100644 compute/compute/snippets/noxfile.py create mode 100644 compute/compute/snippets/quickstart.py create mode 100644 compute/compute/snippets/requirements-test.txt create mode 100644 compute/compute/snippets/requirements.txt create mode 100644 compute/compute/snippets/test_quickstart.py diff --git a/compute/compute/snippets/README.md b/compute/compute/snippets/README.md new file mode 100644 index 00000000000..966f4dfe919 --- /dev/null +++ b/compute/compute/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/compute/snippets/noxfile.py b/compute/compute/snippets/noxfile.py new file mode 100644 index 00000000000..5ff9e1db580 --- /dev/null +++ b/compute/compute/snippets/noxfile.py @@ -0,0 +1,259 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 __future__ import print_function + +import os +from pathlib import Path +import sys +from typing import Callable, Dict, List, Optional + +import nox + + +# WARNING - WARNING - WARNING - WARNING - WARNING +# WARNING - WARNING - WARNING - WARNING - WARNING +# DO NOT EDIT THIS FILE EVER! +# WARNING - WARNING - WARNING - WARNING - WARNING +# WARNING - WARNING - WARNING - WARNING - WARNING + +# Copy `noxfile_config.py` to your directory and modify it instead. + + +# `TEST_CONFIG` dict is a configuration hook that allows users to +# modify the test configurations. The values here should be in sync +# with `noxfile_config.py`. Users will copy `noxfile_config.py` into +# their directory and modify it. + +TEST_CONFIG = { + # You can opt out from the test for specific Python versions. + 'ignored_versions': ["2.7"], + + # Old samples are opted out of enforcing Python type hints + # All new samples should feature them + 'enforce_type_hints': False, + + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + 'gcloud_project_env': 'GOOGLE_CLOUD_PROJECT', + # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + # If you need to use a specific version of pip, + # change pip_version_override to the string representation + # of the version number, for example, "20.2.4" + "pip_version_override": None, + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + 'envs': {}, +} + + +try: + # Ensure we can import noxfile_config in the project's directory. + sys.path.append('.') + from noxfile_config import TEST_CONFIG_OVERRIDE +except ImportError as e: + print("No user noxfile_config found: detail: {}".format(e)) + TEST_CONFIG_OVERRIDE = {} + +# Update the TEST_CONFIG with the user supplied values. +TEST_CONFIG.update(TEST_CONFIG_OVERRIDE) + + +def get_pytest_env_vars() -> Dict[str, str]: + """Returns a dict for pytest invocation.""" + ret = {} + + # Override the GCLOUD_PROJECT and the alias. + env_key = TEST_CONFIG['gcloud_project_env'] + # This should error out if not set. + ret['GOOGLE_CLOUD_PROJECT'] = os.environ[env_key] + + # Apply user supplied envs. + ret.update(TEST_CONFIG['envs']) + return ret + + +# DO NOT EDIT - automatically generated. +# All versions used to tested samples. +ALL_VERSIONS = ["2.7", "3.6", "3.7", "3.8", "3.9"] + +# Any default versions that should be ignored. +IGNORED_VERSIONS = TEST_CONFIG['ignored_versions'] + +TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS]) + +INSTALL_LIBRARY_FROM_SOURCE = bool(os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False)) +# +# Style Checks +# + + +def _determine_local_import_names(start_dir: str) -> List[str]: + """Determines all import names that should be considered "local". + + This is used when running the linter to insure that import order is + properly checked. + """ + file_ext_pairs = [os.path.splitext(path) for path in os.listdir(start_dir)] + return [ + basename + for basename, extension in file_ext_pairs + if extension == ".py" + or os.path.isdir(os.path.join(start_dir, basename)) + and basename not in ("__pycache__") + ] + + +# Linting with flake8. +# +# We ignore the following rules: +# E203: whitespace before ‘:’ +# E266: too many leading ‘#’ for block comment +# E501: line too long +# I202: Additional newline in a section of imports +# +# We also need to specify the rules which are ignored by default: +# ['E226', 'W504', 'E126', 'E123', 'W503', 'E24', 'E704', 'E121'] +FLAKE8_COMMON_ARGS = [ + "--show-source", + "--builtin=gettext", + "--max-complexity=20", + "--import-order-style=google", + "--exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py", + "--ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202", + "--max-line-length=88", +] + + +@nox.session +def lint(session: nox.sessions.Session) -> None: + if not TEST_CONFIG['enforce_type_hints']: + session.install("flake8", "flake8-import-order") + else: + session.install("flake8", "flake8-import-order", "flake8-annotations") + + local_names = _determine_local_import_names(".") + args = FLAKE8_COMMON_ARGS + [ + "--application-import-names", + ",".join(local_names), + "." + ] + session.run("flake8", *args) +# +# Black +# + + +@nox.session +def blacken(session: nox.sessions.Session) -> None: + session.install("black") + python_files = [path for path in os.listdir(".") if path.endswith(".py")] + + session.run("black", *python_files) + +# +# Sample Tests +# + + +PYTEST_COMMON_ARGS = ["--junitxml=sponge_log.xml"] + + +def _session_tests(session: nox.sessions.Session, post_install: Callable = None) -> None: + if TEST_CONFIG["pip_version_override"]: + pip_version = TEST_CONFIG["pip_version_override"] + session.install(f"pip=={pip_version}") + """Runs py.test for a particular project.""" + if os.path.exists("requirements.txt"): + if os.path.exists("constraints.txt"): + session.install("-r", "requirements.txt", "-c", "constraints.txt") + else: + session.install("-r", "requirements.txt") + + if os.path.exists("requirements-test.txt"): + if os.path.exists("constraints-test.txt"): + session.install("-r", "requirements-test.txt", "-c", "constraints-test.txt") + else: + session.install("-r", "requirements-test.txt") + + if INSTALL_LIBRARY_FROM_SOURCE: + session.install("-e", _get_repo_root()) + + if post_install: + post_install(session) + + session.run( + "pytest", + *(PYTEST_COMMON_ARGS + session.posargs), + # Pytest will return 5 when no tests are collected. This can happen + # on travis where slow and flaky tests are excluded. + # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html + success_codes=[0, 5], + env=get_pytest_env_vars() + ) + + +@nox.session(python=ALL_VERSIONS) +def py(session: nox.sessions.Session) -> None: + """Runs py.test for a sample using the specified version of Python.""" + if session.python in TESTED_VERSIONS: + _session_tests(session) + else: + session.skip("SKIPPED: {} tests are disabled for this sample.".format( + session.python + )) + + +# +# Readmegen +# + + +def _get_repo_root() -> Optional[str]: + """ Returns the root folder of the project. """ + # Get root of this repository. Assume we don't have directories nested deeper than 10 items. + p = Path(os.getcwd()) + for i in range(10): + if p is None: + break + if Path(p / ".git").exists(): + return str(p) + # .git is not available in repos cloned via Cloud Build + # setup.py is always in the library's root, so use that instead + # https://github.com/googleapis/synthtool/issues/792 + if Path(p / "setup.py").exists(): + return str(p) + p = p.parent + raise Exception("Unable to detect repository root.") + + +GENERATED_READMES = sorted([x for x in Path(".").rglob("*.rst.in")]) + + +@nox.session +@nox.parametrize("path", GENERATED_READMES) +def readmegen(session: nox.sessions.Session, path: str) -> None: + """(Re-)generates the readme for a sample.""" + session.install("jinja2", "pyyaml") + dir_ = os.path.dirname(path) + + if os.path.exists(os.path.join(dir_, "requirements.txt")): + session.install("-r", os.path.join(dir_, "requirements.txt")) + + in_file = os.path.join(dir_, "README.rst.in") + session.run( + "python", _get_repo_root() + "/scripts/readme-gen/readme_gen.py", in_file + ) diff --git a/compute/compute/snippets/quickstart.py b/compute/compute/snippets/quickstart.py new file mode 100644 index 00000000000..478a5f707a6 --- /dev/null +++ b/compute/compute/snippets/quickstart.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python + +# 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. +""" +A sample script showing how to create, list and delete Google Compute Engine +instances using the google-cloud-compute library. It can be run from command +line to create, list and delete an instance in a given project in a given zone. +""" + +# [START compute_instances_create] +# [START compute_instances_delete] +import sys + +# [START compute_instances_list] +# [START compute_instances_list_all] +# [START compute_instances_operation_check] +import typing + +import google.cloud.compute_v1 as compute_v1 + +# [END compute_instances_operation_check] +# [END compute_instances_list_all] +# [END compute_instances_list] +# [END compute_instances_delete] +# [END compute_instances_create] + + +# [START compute_instances_list] +def list_instances(project_id: str, zone: str) -> typing.Iterable[compute_v1.Instance]: + """ + Gets a list of instances created in given project in given zone. + Returns an iterable collection of Instance objects. + + 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 + + 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] + +# [START compute_instances_list_all] +def list_all_instances( + project_id: str, +) -> typing.Dict[str, typing.Iterable[compute_v1.Instance]]: + """ + Returns a dictionary of all instances present in a project, grouped by their zone. + + Args: + project_id: ID or number of the 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() + agg_list = instance_client.aggregated_list(project=project_id) + all_instances = {} + print("Instances found:") + for zone, response in agg_list: + if response.instances: + all_instances[zone] = 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] + + +# [START compute_instances_create] +def create_instance( + project_id: str, + zone: str, + instance_name: str, + machine_type: str = "n1-standard-1", + source_image: str = "projects/debian-cloud/global/images/family/debian-10", + network_name: str = "global/networks/default", +) -> compute_v1.Instance: + """ + Sends an instance creation request to GCP and waits for it to complete. + + Args: + project_id: ID or number of the project you want to use. + zone: Name of the zone you want to use, for example: us-west3-b + instance_name: Name of the new machine. + 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: Path the the disk image you want to use for your boot + disk. This can be one of the public images + (e.g. "projects/debian-cloud/global/images/family/debian-10") + or a private image you have access to. + You can check the list of available public images using: + $ gcloud compute images list + network_name: Name of the network you want the new instance to use. + For example: global/networks/default - if you want to use the + default network. + + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + + # Every machine requires at least one persistent disk + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = ( + 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_ = compute_v1.AttachedDisk.Type.PERSISTENT + + # Every machine needs to be connected to a VPC network. + # The 'default' network is created automatically in every project. + network_interface = compute_v1.NetworkInterface() + network_interface.name = network_name + + # Collecting all the information into the Instance object + instance = compute_v1.Instance() + instance.name = instance_name + instance.disks = [disk] + full_machine_type_name = f"zones/{zone}/machineTypes/{machine_type}" + instance.machine_type = full_machine_type_name + instance.network_interfaces = [network_interface] + + # Preparing the InsertInstanceRequest + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + print(f"Creating the {instance_name} instance in {zone}...") + operation = instance_client.insert(request=request) + if operation.status == compute_v1.Operation.Status.RUNNING: + operation_client = compute_v1.ZoneOperationsClient() + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +# [END compute_instances_create] + + +# [START compute_instances_delete] +def delete_instance(project_id: str, zone: str, machine_name: str) -> None: + """ + Sends a delete request to GCP and waits for it to complete. + + Args: + project_id: ID or number of the 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 + ) + if operation.status == compute_v1.Operation.Status.RUNNING: + operation_client = compute_v1.ZoneOperationsClient() + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during deletion:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during deletion:", operation.warnings, file=sys.stderr) + print(f"Instance {machine_name} deleted.") + return + + +# [END compute_instances_delete] + + +# [START compute_instances_operation_check] +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: ID or number of the project owning the operation. + + 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 zone, 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] + + +def main(project_id: str, zone: str, instance_name: str) -> None: + + create_instance(project_id, zone, instance_name) + + zone_instances = list_instances(project_id, zone) + print(f"Instances found in {zone}:", ", ".join(i.name for i in zone_instances)) + + all_instances = list_all_instances(project_id) + print(f"Instances found in project {project_id}:") + for i_zone, instances in all_instances.items(): + print(f"{i_zone}:", ", ".join(i.name for i in instances)) + + delete_instance(project_id, zone, instance_name) + + +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" + main(default_project_id, instance_zone, instance_name) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt new file mode 100644 index 00000000000..11b890faecf --- /dev/null +++ b/compute/compute/snippets/requirements-test.txt @@ -0,0 +1 @@ +pytest==6.2.4 \ No newline at end of file diff --git a/compute/compute/snippets/requirements.txt b/compute/compute/snippets/requirements.txt new file mode 100644 index 00000000000..8a2294e1e0a --- /dev/null +++ b/compute/compute/snippets/requirements.txt @@ -0,0 +1 @@ +google-cloud-compute==0.3.0 \ No newline at end of file diff --git a/compute/compute/snippets/test_quickstart.py b/compute/compute/snippets/test_quickstart.py new file mode 100644 index 00000000000..91a2d3642ab --- /dev/null +++ b/compute/compute/snippets/test_quickstart.py @@ -0,0 +1,36 @@ +# 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 re +import typing +import uuid + +import google.auth + +from samples.snippets.quickstart import main + +PROJECT = google.auth.default()[1] +INSTANCE_NAME = "i" + uuid.uuid4().hex[:10] +INSTANCE_ZONE = "europe-central2-b" + + +def test_main(capsys: typing.Any) -> None: + main(PROJECT, INSTANCE_ZONE, INSTANCE_NAME) + + out, _ = capsys.readouterr() + + assert f"Instance {INSTANCE_NAME} created." in out + assert re.search(f"Instances found in {INSTANCE_ZONE}:.+{INSTANCE_NAME}", out) + assert re.search(f"zones/{INSTANCE_ZONE}:.+{INSTANCE_NAME}", out) + assert f"Instance {INSTANCE_NAME} deleted." in out From dc35c09ac5b47d7d8518a3102f3c3ea5f255e926 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Wed, 9 Jun 2021 07:12:34 +0200 Subject: [PATCH 002/113] chore(deps): update dependency google-cloud-compute to v0.4.0 (#62) --- compute/compute/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements.txt b/compute/compute/snippets/requirements.txt index 8a2294e1e0a..d562c687b5d 100644 --- a/compute/compute/snippets/requirements.txt +++ b/compute/compute/snippets/requirements.txt @@ -1 +1 @@ -google-cloud-compute==0.3.0 \ No newline at end of file +google-cloud-compute==0.4.0 \ No newline at end of file From dbb71b99e2b7e048d21354313fc6cb005fce0ac3 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 18 Jun 2021 16:35:36 +0200 Subject: [PATCH 003/113] chore(deps): update dependency google-cloud-compute to v0.4.1 (#68) --- compute/compute/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements.txt b/compute/compute/snippets/requirements.txt index d562c687b5d..c7973f448da 100644 --- a/compute/compute/snippets/requirements.txt +++ b/compute/compute/snippets/requirements.txt @@ -1 +1 @@ -google-cloud-compute==0.4.0 \ No newline at end of file +google-cloud-compute==0.4.1 \ No newline at end of file From a9f9eb6471139cde68cfe9cf92c45f2021bc48f2 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Tue, 29 Jun 2021 21:44:09 +0200 Subject: [PATCH 004/113] feat: adding samples for default values (#64) * feat: adding samples for default values Featuring the usage report API. --- compute/compute/snippets/noxfile.py | 4 +- .../compute/snippets/requirements-test.txt | 3 +- .../compute/snippets/sample_default_values.py | 117 ++++++++++++++++++ .../snippets/test_sample_default_values.py | 65 ++++++++++ 4 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 compute/compute/snippets/sample_default_values.py create mode 100644 compute/compute/snippets/test_sample_default_values.py diff --git a/compute/compute/snippets/noxfile.py b/compute/compute/snippets/noxfile.py index 5ff9e1db580..dc9beecef29 100644 --- a/compute/compute/snippets/noxfile.py +++ b/compute/compute/snippets/noxfile.py @@ -48,8 +48,8 @@ # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a # build specific Cloud project. You can also use your own string # to use your own Cloud project. - 'gcloud_project_env': 'GOOGLE_CLOUD_PROJECT', - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + # 'gcloud_project_env': 'GOOGLE_CLOUD_PROJECT', + 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', # If you need to use a specific version of pip, # change pip_version_override to the string representation # of the version number, for example, "20.2.4" diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index 11b890faecf..1441d3a3786 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1 +1,2 @@ -pytest==6.2.4 \ No newline at end of file +pytest==6.2.4 +google-cloud-storage==1.39.0 \ No newline at end of file diff --git a/compute/compute/snippets/sample_default_values.py b/compute/compute/snippets/sample_default_values.py new file mode 100644 index 00000000000..5f14e2a7781 --- /dev/null +++ b/compute/compute/snippets/sample_default_values.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +# 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. +""" +A sample script showing how to handle default values when communicating +with the Compute Engine API. +""" +# [START compute_instances_verify_default_value] +# [START compute_usage_report_set] +# [START compute_usage_report_get] +# [START compute_usage_report_disable] +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 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( + bucket_name=bucket_name, + 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) + + op_client = compute_v1.GlobalOperationsClient() + op_client.wait(project=project_id, operation=operation.name) +# [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 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() + + # Updating the setting with None will disable the + # usage report generation. + operation = projects_client.set_usage_export_bucket( + project=project_id, usage_export_location_resource=None) + + op_client = compute_v1.GlobalOperationsClient() + op_client.wait(project=project_id, operation=operation.name) +# [END compute_usage_report_disable] diff --git a/compute/compute/snippets/test_sample_default_values.py b/compute/compute/snippets/test_sample_default_values.py new file mode 100644 index 00000000000..b6d2f0acc5d --- /dev/null +++ b/compute/compute/snippets/test_sample_default_values.py @@ -0,0 +1,65 @@ +# 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 typing +import uuid + +import google.auth +import google.cloud.storage as storage +import pytest + +from sample_default_values import \ + disable_usage_export, get_usage_export_bucket, 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) + + +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) + 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) + uel = get_usage_export_bucket(project_id=PROJECT) + assert(uel.bucket_name == '') + assert(uel.report_name_prefix == '') + + +def test_set_usage_export_bucket_custom(capsys: typing.Any, + temp_bucket: storage.Bucket) -> None: + set_usage_export_bucket(project_id=PROJECT, bucket_name=temp_bucket.name, + report_name_prefix=TEST_PREFIX) + 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) + uel = get_usage_export_bucket(project_id=PROJECT) + assert(uel.bucket_name == '') + assert(uel.report_name_prefix == '') From f66ff115d0eeee3d7d2d3acb760a0c62e59621c8 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Thu, 1 Jul 2021 12:10:25 +0200 Subject: [PATCH 005/113] chore(deps): update dependency google-cloud-storage to v1.40.0 (#72) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [google-cloud-storage](https://togithub.com/googleapis/python-storage) | `==1.39.0` -> `==1.40.0` | [![age](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.40.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.40.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.40.0/compatibility-slim/1.39.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.40.0/confidence-slim/1.39.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
googleapis/python-storage ### [`v1.40.0`](https://togithub.com/googleapis/python-storage/blob/master/CHANGELOG.md#​1400-httpswwwgithubcomgoogleapispython-storagecomparev1390v1400-2021-06-30) [Compare Source](https://togithub.com/googleapis/python-storage/compare/v1.39.0...v1.40.0) ##### Features - add preconditions and retry configuration to blob.create_resumable_upload_session ([#​484](https://www.github.com/googleapis/python-storage/issues/484)) ([0ae35ee](https://www.github.com/googleapis/python-storage/commit/0ae35eef0fe82fe60bc095c4b183102bb1dabeeb)) - add public access prevention to bucket IAM configuration ([#​304](https://www.github.com/googleapis/python-storage/issues/304)) ([e3e57a9](https://www.github.com/googleapis/python-storage/commit/e3e57a9c779d6b87852063787f19e27c76b1bb14)) ##### Bug Fixes - replace default retry for upload operations ([#​480](https://www.github.com/googleapis/python-storage/issues/480)) ([c027ccf](https://www.github.com/googleapis/python-storage/commit/c027ccf4279fb05e041754294f10744b7d81beea))
--- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/python-compute). --- compute/compute/snippets/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index 1441d3a3786..84944a1a667 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,2 +1,2 @@ pytest==6.2.4 -google-cloud-storage==1.39.0 \ No newline at end of file +google-cloud-storage==1.40.0 \ No newline at end of file From 88b75bc435a6acfa942563934439c4a9f9939af7 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Fri, 2 Jul 2021 20:08:22 +0200 Subject: [PATCH 006/113] docs: samples docstring and comments update (#69) * chore: Updating docstrings to match samples from other languages. * feat: Fixing comments and docstrings to be in sync with other samples. * docs: Updating sample docstrings and comments Applying some changes after consulting with tech writer. --- compute/compute/snippets/quickstart.py | 75 ++++++++++---------------- 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/compute/compute/snippets/quickstart.py b/compute/compute/snippets/quickstart.py index 478a5f707a6..fb67bfab0d9 100644 --- a/compute/compute/snippets/quickstart.py +++ b/compute/compute/snippets/quickstart.py @@ -40,13 +40,11 @@ # [START compute_instances_list] def list_instances(project_id: str, zone: str) -> typing.Iterable[compute_v1.Instance]: """ - Gets a list of instances created in given project in given zone. - Returns an iterable collection of Instance objects. + List all instances in the given zone in the specified project. 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 - + 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. """ @@ -58,20 +56,18 @@ def list_instances(project_id: str, zone: str) -> typing.Iterable[compute_v1.Ins print(f" - {instance.name} ({instance.machine_type})") return instance_list - - # [END compute_instances_list] + # [START compute_instances_list_all] def list_all_instances( project_id: str, ) -> typing.Dict[str, typing.Iterable[compute_v1.Instance]]: """ - Returns a dictionary of all instances present in a project, grouped by their zone. + Return a dictionary of all instances present in a project, grouped by their zone. Args: - project_id: ID or number of the project you want to use. - + 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. @@ -87,8 +83,6 @@ def list_all_instances( for instance in response.instances: print(f" - {instance.name} ({instance.machine_type})") return all_instances - - # [END compute_instances_list_all] @@ -102,33 +96,28 @@ def create_instance( network_name: str = "global/networks/default", ) -> compute_v1.Instance: """ - Sends an instance creation request to GCP and waits for it to complete. + Send an instance creation request to the Compute Engine API and wait for it to complete. Args: - project_id: ID or number of the project you want to use. - zone: Name of the zone you want to use, for example: us-west3-b - instance_name: Name of the new machine. - 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: Path the the disk image you want to use for your boot + 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 new virtual machine. + 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" + source_image: path to the operating system image to mount on your boot disk. This can be one of the public images - (e.g. "projects/debian-cloud/global/images/family/debian-10") + (like "projects/debian-cloud/global/images/family/debian-10") or a private image you have access to. - You can check the list of available public images using: - $ gcloud compute images list - network_name: Name of the network you want the new instance to use. - For example: global/networks/default - if you want to use the - default network. - + network_name: name of the network you want the new instance to use. + For example: "global/networks/default" represents the `default` + network interface, which is created automatically for each project. Returns: Instance object. """ instance_client = compute_v1.InstancesClient() - # Every machine requires at least one persistent disk + # Describe 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 = ( @@ -140,12 +129,11 @@ def create_instance( disk.boot = True disk.type_ = compute_v1.AttachedDisk.Type.PERSISTENT - # Every machine needs to be connected to a VPC network. - # The 'default' network is created automatically in every project. + # Use the network interface provided in the network_name argument. network_interface = compute_v1.NetworkInterface() network_interface.name = network_name - # Collecting all the information into the Instance object + # Collect information into the Instance object. instance = compute_v1.Instance() instance.name = instance_name instance.disks = [disk] @@ -153,12 +141,13 @@ def create_instance( instance.machine_type = full_machine_type_name instance.network_interfaces = [network_interface] - # Preparing the InsertInstanceRequest + # 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) if operation.status == compute_v1.Operation.Status.RUNNING: @@ -172,20 +161,18 @@ def create_instance( print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") return instance - - # [END compute_instances_create] # [START compute_instances_delete] def delete_instance(project_id: str, zone: str, machine_name: str) -> None: """ - Sends a delete request to GCP and waits for it to complete. + Send an instance deletion request to the Compute Engine API and wait for it to complete. Args: - project_id: ID or number of the 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. + 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() @@ -204,8 +191,6 @@ def delete_instance(project_id: str, zone: str, machine_name: str) -> None: print("Warning during deletion:", operation.warnings, file=sys.stderr) print(f"Instance {machine_name} deleted.") return - - # [END compute_instances_delete] @@ -220,7 +205,7 @@ def wait_for_operation( Args: operation: The Operation object representing the operation you want to wait on. - project_id: ID or number of the project owning the operation. + project_id: project ID or project number of the Cloud project you want to use. Returns: Finished Operation object. @@ -232,13 +217,11 @@ def wait_for_operation( kwargs["zone"] = operation.zone.rsplit("/", maxsplit=1)[1] elif operation.region: client = compute_v1.RegionOperationsClient() - # Operation.region is a full URL address of a zone, so we need to extract just the name + # 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] From eb57021332727948edefd8a81747c3a22442196f Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Thu, 15 Jul 2021 15:33:57 +0200 Subject: [PATCH 007/113] chore(deps): update dependency google-cloud-storage to v1.41.0 (#76) --- compute/compute/snippets/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index 84944a1a667..766c3d3360a 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,2 +1,2 @@ pytest==6.2.4 -google-cloud-storage==1.40.0 \ No newline at end of file +google-cloud-storage==1.41.0 \ No newline at end of file From 6ca837d1e23e37194c1953d6c5c4a9b1f0497f79 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Wed, 21 Jul 2021 07:56:12 -0700 Subject: [PATCH 008/113] chore: update templated files --- compute/compute/snippets/noxfile.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compute/compute/snippets/noxfile.py b/compute/compute/snippets/noxfile.py index dc9beecef29..6a8ccdae22c 100644 --- a/compute/compute/snippets/noxfile.py +++ b/compute/compute/snippets/noxfile.py @@ -28,8 +28,9 @@ # WARNING - WARNING - WARNING - WARNING - WARNING # WARNING - WARNING - WARNING - WARNING - WARNING -# Copy `noxfile_config.py` to your directory and modify it instead. +BLACK_VERSION = "black==19.10b0" +# Copy `noxfile_config.py` to your directory and modify it instead. # `TEST_CONFIG` dict is a configuration hook that allows users to # modify the test configurations. The values here should be in sync @@ -48,8 +49,8 @@ # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a # build specific Cloud project. You can also use your own string # to use your own Cloud project. - # 'gcloud_project_env': 'GOOGLE_CLOUD_PROJECT', - 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + 'gcloud_project_env': 'GOOGLE_CLOUD_PROJECT', + # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', # If you need to use a specific version of pip, # change pip_version_override to the string representation # of the version number, for example, "20.2.4" @@ -159,7 +160,7 @@ def lint(session: nox.sessions.Session) -> None: @nox.session def blacken(session: nox.sessions.Session) -> None: - session.install("black") + session.install(BLACK_VERSION) python_files = [path for path in os.listdir(".") if path.endswith(".py")] session.run("black", *python_files) From df25add918f97c6dc0cc412357599ed7da0884fb Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Thu, 22 Jul 2021 15:06:09 +0200 Subject: [PATCH 009/113] fix: Kokoro uses separate project for concurent tests. (#83) Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [x] Ensure the tests and linter pass - [x] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary) Making sure that synthtool won't revert the `gcloud_project_env` setting for sample tests like it did in https://github.com/googleapis/python-compute/pull/67 --- compute/compute/snippets/noxfile_config.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 compute/compute/snippets/noxfile_config.py diff --git a/compute/compute/snippets/noxfile_config.py b/compute/compute/snippets/noxfile_config.py new file mode 100644 index 00000000000..16c4c694efc --- /dev/null +++ b/compute/compute/snippets/noxfile_config.py @@ -0,0 +1,18 @@ +# 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. + +TEST_CONFIG_OVERRIDE = { + # Tests in test_sample_default_values.py require separate projects to not interfere with each other. + 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', +} From 07cb9e5c49dec81fe71d6fc0795a4295a19d7fe4 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 26 Jul 2021 20:48:42 +0200 Subject: [PATCH 010/113] chore(deps): update dependency google-cloud-storage to v1.41.1 (#82) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [google-cloud-storage](https://togithub.com/googleapis/python-storage) | `==1.41.0` -> `==1.41.1` | [![age](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.41.1/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.41.1/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.41.1/compatibility-slim/1.41.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.41.1/confidence-slim/1.41.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
googleapis/python-storage ### [`v1.41.1`](https://togithub.com/googleapis/python-storage/blob/master/CHANGELOG.md#​1411-httpswwwgithubcomgoogleapispython-storagecomparev1410v1411-2021-07-20) [Compare Source](https://togithub.com/googleapis/python-storage/compare/v1.41.0...v1.41.1)
--- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Renovate will not automatically rebase this PR, because other commits have been found. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/python-compute). --- compute/compute/snippets/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index 766c3d3360a..444dff8b324 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,2 +1,2 @@ pytest==6.2.4 -google-cloud-storage==1.41.0 \ No newline at end of file +google-cloud-storage==1.41.1 \ No newline at end of file From 70529d217fa3b4b0c956bf19ac5887051d5c6257 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 27 Jul 2021 18:01:27 +0200 Subject: [PATCH 011/113] chore(deps): update dependency google-cloud-compute to v0.4.2 (#87) --- compute/compute/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements.txt b/compute/compute/snippets/requirements.txt index c7973f448da..102dfdfe824 100644 --- a/compute/compute/snippets/requirements.txt +++ b/compute/compute/snippets/requirements.txt @@ -1 +1 @@ -google-cloud-compute==0.4.1 \ No newline at end of file +google-cloud-compute==0.4.2 \ No newline at end of file From 7455de4b723c6e24b74f6e0ea4428427a96ae96d Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 4 Aug 2021 12:14:01 +0200 Subject: [PATCH 012/113] feat: adding samples to start/stop/reset operations (#75) * feat: Adding start/stop compute samples. Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/sample_start_stop.py | 116 ++++++++++++++ .../snippets/test_sample_default_values.py | 5 + .../snippets/test_sample_start_stop.py | 150 ++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 compute/compute/snippets/sample_start_stop.py create mode 100644 compute/compute/snippets/test_sample_start_stop.py diff --git a/compute/compute/snippets/sample_start_stop.py b/compute/compute/snippets/sample_start_stop.py new file mode 100644 index 00000000000..815f84ce2c2 --- /dev/null +++ b/compute/compute/snippets/sample_start_stop.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python + +# 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. + +""" +A sample script showing how to start and stop Google Compute Engine instances. +""" +from google.cloud import compute_v1 + + +# [START compute_start_instance] +def start_instance(project_id: str, zone: str, instance_name: str): + """ + 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() + op_client = compute_v1.ZoneOperationsClient() + + op = instance_client.start(project=project_id, zone=zone, instance=instance_name) + + op_client.wait(project=project_id, zone=zone, operation=op.name) + return +# [END compute_start_instance] + + +# [START compute_start_enc_instance] +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() + op_client = compute_v1.ZoneOperationsClient() + + 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] + + op = instance_client.start_with_encryption_key(project=project_id, zone=zone, instance=instance_name, + instances_start_with_encryption_key_request_resource=enc_data) + + op_client.wait(project=project_id, zone=zone, operation=op.name) + return +# [END compute_start_enc_instance] + + +# [START compute_stop_instance] +def stop_instance(project_id: str, zone: str, instance_name: str): + """ + Stops a stopped 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() + op_client = compute_v1.ZoneOperationsClient() + + op = instance_client.stop(project=project_id, zone=zone, instance=instance_name) + + op_client.wait(project=project_id, zone=zone, operation=op.name) + return +# [END compute_stop_instance] + + +# [START compute_reset_instance] +def reset_instance(project_id: str, zone: str, instance_name: str): + """ + 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() + op_client = compute_v1.ZoneOperationsClient() + + op = instance_client.reset(project=project_id, zone=zone, instance=instance_name) + + op_client.wait(project=project_id, zone=zone, operation=op.name) + return +# [END compute_reset_instance] diff --git a/compute/compute/snippets/test_sample_default_values.py b/compute/compute/snippets/test_sample_default_values.py index b6d2f0acc5d..613c6efa39f 100644 --- a/compute/compute/snippets/test_sample_default_values.py +++ b/compute/compute/snippets/test_sample_default_values.py @@ -11,6 +11,7 @@ # WITHOUT 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 @@ -37,6 +38,7 @@ def temp_bucket(): 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') @@ -44,6 +46,7 @@ def test_set_usage_export_bucket_default(capsys: typing.Any, 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 == '') @@ -53,6 +56,7 @@ def test_set_usage_export_bucket_custom(capsys: typing.Any, temp_bucket: storage.Bucket) -> None: 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) @@ -60,6 +64,7 @@ def test_set_usage_export_bucket_custom(capsys: typing.Any, 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/compute/snippets/test_sample_start_stop.py b/compute/compute/snippets/test_sample_start_stop.py new file mode 100644 index 00000000000..ea28bfa3f74 --- /dev/null +++ b/compute/compute/snippets/test_sample_start_stop.py @@ -0,0 +1,150 @@ +# 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 samples.snippets.sample_start_stop import start_instance, start_instance_with_encryption_key, 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_ = compute_v1.AttachedDisk.Type.PERSISTENT + + 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(request=request) + 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(project=PROJECT, zone=INSTANCE_ZONE, instance=instance.name) + 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) + + +def test_instance_operations(compute_instance): + assert _get_status(compute_instance) == compute_v1.Instance.Status.RUNNING + + stop_instance(PROJECT, INSTANCE_ZONE, compute_instance.name) + + while _get_status(compute_instance) == compute_v1.Instance.Status.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) == compute_v1.Instance.Status.TERMINATED + + start_instance(PROJECT, INSTANCE_ZONE, compute_instance.name) + assert _get_status(compute_instance) == compute_v1.Instance.Status.RUNNING + + +def test_instance_encrypted(compute_encrypted_instance): + assert _get_status(compute_encrypted_instance) == compute_v1.Instance.Status.RUNNING + + stop_instance(PROJECT, INSTANCE_ZONE, compute_encrypted_instance.name) + while _get_status(compute_encrypted_instance) == compute_v1.Instance.Status.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) == compute_v1.Instance.Status.TERMINATED + + start_instance_with_encryption_key(PROJECT, INSTANCE_ZONE, compute_encrypted_instance.name, KEY_B64) + assert _get_status(compute_encrypted_instance) == compute_v1.Instance.Status.RUNNING From 41ab432aeb9348792aac65fa31719f3019786c8e Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Mon, 9 Aug 2021 07:55:23 -0700 Subject: [PATCH 013/113] fix!: uint64/int64 fields accept ints instead of strings (#92) feat: add always_use_jwt_access fix: fix required query params handling (closes #12) fix!: uint64/int64 fields accept ints instead of strings Release-As: 0.5.0 --- compute/compute/snippets/quickstart.py | 2 +- compute/compute/snippets/test_sample_start_stop.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compute/compute/snippets/quickstart.py b/compute/compute/snippets/quickstart.py index fb67bfab0d9..341217e6233 100644 --- a/compute/compute/snippets/quickstart.py +++ b/compute/compute/snippets/quickstart.py @@ -123,7 +123,7 @@ def create_instance( initialize_params.source_image = ( source_image # "projects/debian-cloud/global/images/family/debian-10" ) - initialize_params.disk_size_gb = "10" + initialize_params.disk_size_gb = 10 disk.initialize_params = initialize_params disk.auto_delete = True disk.boot = True diff --git a/compute/compute/snippets/test_sample_start_stop.py b/compute/compute/snippets/test_sample_start_stop.py index ea28bfa3f74..ba01dd9eec8 100644 --- a/compute/compute/snippets/test_sample_start_stop.py +++ b/compute/compute/snippets/test_sample_start_stop.py @@ -38,7 +38,7 @@ def _make_disk(raw_key: bytes = None): initialize_params.source_image = ( "projects/debian-cloud/global/images/family/debian-10" ) - initialize_params.disk_size_gb = "10" + initialize_params.disk_size_gb = 10 disk.initialize_params = initialize_params disk.auto_delete = True disk.boot = True From 1a633f299f8f8fbdf10252843294bc3ac7a89fca Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 9 Aug 2021 18:11:55 +0200 Subject: [PATCH 014/113] chore(deps): update dependency google-cloud-compute to v0.5.0 (#97) --- compute/compute/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements.txt b/compute/compute/snippets/requirements.txt index 102dfdfe824..209ab22d71a 100644 --- a/compute/compute/snippets/requirements.txt +++ b/compute/compute/snippets/requirements.txt @@ -1 +1 @@ -google-cloud-compute==0.4.2 \ No newline at end of file +google-cloud-compute==0.5.0 \ No newline at end of file From 3397e2162a06f8f66d531027ee34d2d96c54d44f Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Tue, 10 Aug 2021 04:11:51 -0700 Subject: [PATCH 015/113] chore: fix INSTALL_LIBRARY_FROM_SOURCE in noxfile.py (#98) --- compute/compute/snippets/noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/noxfile.py b/compute/compute/snippets/noxfile.py index 6a8ccdae22c..125bb619cc4 100644 --- a/compute/compute/snippets/noxfile.py +++ b/compute/compute/snippets/noxfile.py @@ -96,7 +96,7 @@ def get_pytest_env_vars() -> Dict[str, str]: TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS]) -INSTALL_LIBRARY_FROM_SOURCE = bool(os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False)) +INSTALL_LIBRARY_FROM_SOURCE = os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False) in ("True", "true") # # Style Checks # From 246e5624ca4dfed414dc9ff36773e4c727992942 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Tue, 10 Aug 2021 19:05:56 +0200 Subject: [PATCH 016/113] docs(samples): Adding pagination sample. (#78) --- compute/compute/snippets/quickstart.py | 7 +- compute/compute/snippets/sample_pagination.py | 80 +++++++++++++++++++ .../snippets/test_sample_pagination.py | 30 +++++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 compute/compute/snippets/sample_pagination.py create mode 100644 compute/compute/snippets/test_sample_pagination.py diff --git a/compute/compute/snippets/quickstart.py b/compute/compute/snippets/quickstart.py index 341217e6233..462733bc3f1 100644 --- a/compute/compute/snippets/quickstart.py +++ b/compute/compute/snippets/quickstart.py @@ -73,9 +73,14 @@ def list_all_instances( iterable collections of Instance objects as values. """ instance_client = compute_v1.InstancesClient() - agg_list = instance_client.aggregated_list(project=project_id) + # Use the `max_results` parameter to limit the number of results that the API returns per response page. + request = compute_v1.AggregatedListInstancesRequest(project=project_id, max_results=5) + agg_list = instance_client.aggregated_list(request=request) all_instances = {} 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] = response.instances diff --git a/compute/compute/snippets/sample_pagination.py b/compute/compute/snippets/sample_pagination.py new file mode 100644 index 00000000000..99ab80b09c1 --- /dev/null +++ b/compute/compute/snippets/sample_pagination.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python + +# 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. + +# [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) -> None: + """ + 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: + None. + """ + 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=3, + filter="deprecated.state != DEPRECATED") + + # 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}") +# [END compute_images_list ] + + +# [START compute_images_list_page ] +def print_images_list_by_page(project: str, page_size: int = 10) -> None: + """ + 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: + None. + """ + 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") + + # 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}: ") + for img in page.items: + print(f" - {img.name}") +# [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/compute/snippets/test_sample_pagination.py b/compute/compute/snippets/test_sample_pagination.py new file mode 100644 index 00000000000..c881ccd1ae9 --- /dev/null +++ b/compute/compute/snippets/test_sample_pagination.py @@ -0,0 +1,30 @@ +# 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 typing + +from sample_pagination import print_images_list, print_images_list_by_page + +PROJECT = 'windows-sql-cloud' + + +def test_pagination(capsys: typing.Any) -> None: + print_images_list(PROJECT) + out, _ = capsys.readouterr() + assert(len(out.splitlines()) > 2) + + +def test_pagination_page(capsys: typing.Any) -> None: + print_images_list_by_page(PROJECT, 2) + out, _ = capsys.readouterr() + assert("Page 2" in out) From 720e9c3f5bf83d78f2af4158fdb022a5a52d9c85 Mon Sep 17 00:00:00 2001 From: Fedor Isakov Date: Thu, 12 Aug 2021 13:54:48 +0300 Subject: [PATCH 017/113] chore(samples): fix LRO wrapper (#99) --- compute/compute/snippets/quickstart.py | 8 ++++---- .../compute/snippets/sample_default_values.py | 13 ++++++++++-- compute/compute/snippets/sample_start_stop.py | 20 +++++++++++++++---- .../snippets/test_sample_start_stop.py | 10 ++++++++-- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/compute/compute/snippets/quickstart.py b/compute/compute/snippets/quickstart.py index 462733bc3f1..74226cda3f0 100644 --- a/compute/compute/snippets/quickstart.py +++ b/compute/compute/snippets/quickstart.py @@ -121,6 +121,7 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() + operation_client = compute_v1.ZoneOperationsClient() # Describe the size and source image of the boot disk to attach to the instance. disk = compute_v1.AttachedDisk() @@ -155,8 +156,7 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert(request=request) - if operation.status == compute_v1.Operation.Status.RUNNING: - operation_client = compute_v1.ZoneOperationsClient() + while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) @@ -180,13 +180,13 @@ def delete_instance(project_id: str, zone: str, machine_name: str) -> None: machine_name: name of the machine you want to delete. """ instance_client = compute_v1.InstancesClient() + operation_client = compute_v1.ZoneOperationsClient() print(f"Deleting {machine_name} from {zone}...") operation = instance_client.delete( project=project_id, zone=zone, instance=machine_name ) - if operation.status == compute_v1.Operation.Status.RUNNING: - operation_client = compute_v1.ZoneOperationsClient() + while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) diff --git a/compute/compute/snippets/sample_default_values.py b/compute/compute/snippets/sample_default_values.py index 5f14e2a7781..53dbaf62d0a 100644 --- a/compute/compute/snippets/sample_default_values.py +++ b/compute/compute/snippets/sample_default_values.py @@ -59,7 +59,12 @@ def set_usage_export_bucket(project_id: str, bucket_name: str, project=project_id, usage_export_location_resource=usage_export_location) op_client = compute_v1.GlobalOperationsClient() - op_client.wait(project=project_id, operation=operation.name) + + while operation.status != compute_v1.Operation.Status.DONE: + operation = op_client.wait( + operation=operation.name, project=project_id + ) + # [END compute_usage_report_set] @@ -113,5 +118,9 @@ def disable_usage_export(project_id: str) -> None: project=project_id, usage_export_location_resource=None) op_client = compute_v1.GlobalOperationsClient() - op_client.wait(project=project_id, operation=operation.name) + + while operation.status != compute_v1.Operation.Status.DONE: + operation = op_client.wait( + operation=operation.name, project=project_id + ) # [END compute_usage_report_disable] diff --git a/compute/compute/snippets/sample_start_stop.py b/compute/compute/snippets/sample_start_stop.py index 815f84ce2c2..d5bee8f0544 100644 --- a/compute/compute/snippets/sample_start_stop.py +++ b/compute/compute/snippets/sample_start_stop.py @@ -35,7 +35,10 @@ def start_instance(project_id: str, zone: str, instance_name: str): op = instance_client.start(project=project_id, zone=zone, instance=instance_name) - op_client.wait(project=project_id, zone=zone, operation=op.name) + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait( + operation=op.name, zone=zone, project=project_id + ) return # [END compute_start_instance] @@ -71,7 +74,10 @@ def start_instance_with_encryption_key(project_id: str, zone: str, instance_name op = instance_client.start_with_encryption_key(project=project_id, zone=zone, instance=instance_name, instances_start_with_encryption_key_request_resource=enc_data) - op_client.wait(project=project_id, zone=zone, operation=op.name) + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait( + operation=op.name, zone=zone, project=project_id + ) return # [END compute_start_enc_instance] @@ -91,7 +97,10 @@ def stop_instance(project_id: str, zone: str, instance_name: str): op = instance_client.stop(project=project_id, zone=zone, instance=instance_name) - op_client.wait(project=project_id, zone=zone, operation=op.name) + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait( + operation=op.name, zone=zone, project=project_id + ) return # [END compute_stop_instance] @@ -111,6 +120,9 @@ def reset_instance(project_id: str, zone: str, instance_name: str): op = instance_client.reset(project=project_id, zone=zone, instance=instance_name) - op_client.wait(project=project_id, zone=zone, operation=op.name) + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait( + operation=op.name, zone=zone, project=project_id + ) return # [END compute_reset_instance] diff --git a/compute/compute/snippets/test_sample_start_stop.py b/compute/compute/snippets/test_sample_start_stop.py index ba01dd9eec8..a2f2bf37753 100644 --- a/compute/compute/snippets/test_sample_start_stop.py +++ b/compute/compute/snippets/test_sample_start_stop.py @@ -77,7 +77,10 @@ def _create_instance(request: compute_v1.InsertInstanceRequest): operation_client = compute_v1.ZoneOperationsClient() operation = instance_client.insert(request=request) - operation_client.wait(operation=operation.name, zone=INSTANCE_ZONE, project=PROJECT) + 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) @@ -87,7 +90,10 @@ def _delete_instance(instance: compute_v1.Instance): operation_client = compute_v1.ZoneOperationsClient() operation = instance_client.delete(project=PROJECT, zone=INSTANCE_ZONE, instance=instance.name) - operation_client.wait(operation=operation.name, zone=INSTANCE_ZONE, project=PROJECT) + 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: From c561363081ff974c10ed15679809c76efcb15cf1 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Thu, 12 Aug 2021 22:46:20 +0200 Subject: [PATCH 018/113] chore(deps): update dependency google-cloud-storage to v1.42.0 (#103) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [google-cloud-storage](https://togithub.com/googleapis/python-storage) | `==1.41.1` -> `==1.42.0` | [![age](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.42.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.42.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.42.0/compatibility-slim/1.41.1)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.42.0/confidence-slim/1.41.1)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
googleapis/python-storage ### [`v1.42.0`](https://togithub.com/googleapis/python-storage/blob/master/CHANGELOG.md#​1420-httpswwwgithubcomgoogleapispython-storagecomparev1411v1420-2021-08-05) [Compare Source](https://togithub.com/googleapis/python-storage/compare/v1.41.1...v1.42.0) ##### Features - add 'page_size' parameter to 'Bucket.list_blobs, list_buckets ([#​520](https://www.github.com/googleapis/python-storage/issues/520)) ([c5f4ad8](https://www.github.com/googleapis/python-storage/commit/c5f4ad8fddd1849a4229b0126c4c022bccb90128)) ##### Bug Fixes - **deps:** add explicit ranges for 'google-api-core' and 'google-auth' ([#​530](https://www.github.com/googleapis/python-storage/issues/530)) ([310f207](https://www.github.com/googleapis/python-storage/commit/310f207411da0382af310172344f19c644c14e6a)) - downloading no longer marks metadata fields as 'changed' ([#​523](https://www.github.com/googleapis/python-storage/issues/523)) ([160d1ec](https://www.github.com/googleapis/python-storage/commit/160d1ecb41f1f269b25cb68b2d2f7daf418bf01c)) - make 'requests.exceptions.ChunkedEncodingError retryable by default ([#​526](https://www.github.com/googleapis/python-storage/issues/526)) ([4abb403](https://www.github.com/googleapis/python-storage/commit/4abb40310eca7ec45afc4bc5e4dfafbe083e74d2)) ##### Documentation - update supported / removed Python versions in README ([#​519](https://www.github.com/googleapis/python-storage/issues/519)) ([1f1b138](https://www.github.com/googleapis/python-storage/commit/1f1b138865fb171535ee0cf768aff1987ed58914)) ##### [1.41.1](https://www.github.com/googleapis/python-storage/compare/v1.41.0...v1.41.1) (2021-07-20) ##### Bug Fixes - **deps:** pin `{api,cloud}-core`, `auth` to allow 2.x versions on Python 3 ([#​512](https://www.github.com/googleapis/python-storage/issues/512)) ([4d7500e](https://www.github.com/googleapis/python-storage/commit/4d7500e39c51efd817b8363b69c88be040f3edb8)) - remove trailing commas from error message constants ([#​505](https://www.github.com/googleapis/python-storage/issues/505)) ([d4a86ce](https://www.github.com/googleapis/python-storage/commit/d4a86ceb7a7c5e00ba7bae37c7078d52478040ff)), closes [#​501](https://www.github.com/googleapis/python-storage/issues/501) ##### Documentation - replace usage of deprecated function `download_as_string` in docs ([#​508](https://www.github.com/googleapis/python-storage/issues/508)) ([8dfa4d4](https://www.github.com/googleapis/python-storage/commit/8dfa4d429dce94b671dc3e3755e52ab82733f61a))
--- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/python-compute). --- compute/compute/snippets/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index 444dff8b324..ead482a5f7c 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,2 +1,2 @@ pytest==6.2.4 -google-cloud-storage==1.41.1 \ No newline at end of file +google-cloud-storage==1.42.0 \ No newline at end of file From 911c203b44bac272042efd497d92cedbd95d239a Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Mon, 16 Aug 2021 10:50:08 -0700 Subject: [PATCH 019/113] chore: remove 2.7 from samples noxfile (#106) See: https://github.com/googleapis/synthtool/pull/1173 --- compute/compute/snippets/noxfile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compute/compute/snippets/noxfile.py b/compute/compute/snippets/noxfile.py index 125bb619cc4..e73436a1562 100644 --- a/compute/compute/snippets/noxfile.py +++ b/compute/compute/snippets/noxfile.py @@ -39,7 +39,7 @@ TEST_CONFIG = { # You can opt out from the test for specific Python versions. - 'ignored_versions': ["2.7"], + 'ignored_versions': [], # Old samples are opted out of enforcing Python type hints # All new samples should feature them @@ -88,8 +88,8 @@ def get_pytest_env_vars() -> Dict[str, str]: # DO NOT EDIT - automatically generated. -# All versions used to tested samples. -ALL_VERSIONS = ["2.7", "3.6", "3.7", "3.8", "3.9"] +# All versions used to test samples. +ALL_VERSIONS = ["3.6", "3.7", "3.8", "3.9"] # Any default versions that should be ignored. IGNORED_VERSIONS = TEST_CONFIG['ignored_versions'] From 84664d44520ae6c08de40f28dc35ea365e8ed702 Mon Sep 17 00:00:00 2001 From: Jonathan L Date: Wed, 18 Aug 2021 01:12:23 -0700 Subject: [PATCH 020/113] fix: correct region tag formatting (#102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/python-compute/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass - [x] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary) Fixes # 🦕 --- compute/compute/snippets/sample_pagination.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compute/compute/snippets/sample_pagination.py b/compute/compute/snippets/sample_pagination.py index 99ab80b09c1..378d0512dae 100644 --- a/compute/compute/snippets/sample_pagination.py +++ b/compute/compute/snippets/sample_pagination.py @@ -14,14 +14,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -# [START compute_images_list_page ] -# [START compute_images_list ] +# [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 ] +# [END compute_images_list] +# [END compute_images_list_page] -# [START compute_images_list ] +# [START compute_images_list] def print_images_list(project: str) -> None: """ Prints a list of all non-deprecated image names available in given project. @@ -42,10 +42,10 @@ def print_images_list(project: str) -> None: # 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}") -# [END compute_images_list ] +# [END compute_images_list] -# [START compute_images_list_page ] +# [START compute_images_list_page] def print_images_list_by_page(project: str, page_size: int = 10) -> None: """ Prints a list of all non-deprecated image names available in a given project, @@ -70,7 +70,7 @@ def print_images_list_by_page(project: str, page_size: int = 10) -> None: print(f"Page {page_num}: ") for img in page.items: print(f" - {img.name}") -# [END compute_images_list_page ] +# [END compute_images_list_page] if __name__ == '__main__': From a308ef557292c9bf8b0a4d531460384217a64be9 Mon Sep 17 00:00:00 2001 From: Remigiusz Samborski Date: Fri, 20 Aug 2021 15:54:22 +0200 Subject: [PATCH 021/113] fix(compute): Updated max_results to 100 to avoid too many API calls (#108) Based on @amanda-tarafa comment in C# sample (https://github.com/GoogleCloudPlatform/dotnet-docs-samples/pull/1445/files#r687921807) I am changing the max_results value to 100 --- compute/compute/snippets/sample_pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/sample_pagination.py b/compute/compute/snippets/sample_pagination.py index 378d0512dae..2782a3c3ae3 100644 --- a/compute/compute/snippets/sample_pagination.py +++ b/compute/compute/snippets/sample_pagination.py @@ -34,7 +34,7 @@ def print_images_list(project: str) -> None: """ 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=3, + images_list_request = compute_v1.ListImagesRequest(project=project, max_results=100, filter="deprecated.state != DEPRECATED") # Although the `max_results` parameter is specified in the request, the iterable returned From 26efae4f9e5c2f6b932ed2f8eb0e8620a5323ef3 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 31 Aug 2021 17:38:15 +0200 Subject: [PATCH 022/113] chore(deps): update dependency pytest to v6.2.5 (#112) Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index ead482a5f7c..7d2834c42ad 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,2 +1,2 @@ -pytest==6.2.4 +pytest==6.2.5 google-cloud-storage==1.42.0 \ No newline at end of file From 20ac50886a92c86904482714084d05d68c2ee27a Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Thu, 9 Sep 2021 16:43:17 +0200 Subject: [PATCH 023/113] chore(deps): update dependency google-cloud-storage to v1.42.1 (#119) --- compute/compute/snippets/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index 7d2834c42ad..394c07aa632 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,2 +1,2 @@ pytest==6.2.5 -google-cloud-storage==1.42.0 \ No newline at end of file +google-cloud-storage==1.42.1 \ No newline at end of file From ee88292a992d1d8b1d35954df622ddc6dec5fb08 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Fri, 17 Sep 2021 08:19:07 -0700 Subject: [PATCH 024/113] chore: blacken samples noxfile template (#121) --- compute/compute/snippets/noxfile.py | 44 ++++++++++++++++------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/compute/compute/snippets/noxfile.py b/compute/compute/snippets/noxfile.py index e73436a1562..b008613f03f 100644 --- a/compute/compute/snippets/noxfile.py +++ b/compute/compute/snippets/noxfile.py @@ -39,17 +39,15 @@ TEST_CONFIG = { # You can opt out from the test for specific Python versions. - 'ignored_versions': [], - + "ignored_versions": [], # Old samples are opted out of enforcing Python type hints # All new samples should feature them - 'enforce_type_hints': False, - + "enforce_type_hints": False, # An envvar key for determining the project id to use. Change it # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a # build specific Cloud project. You can also use your own string # to use your own Cloud project. - 'gcloud_project_env': 'GOOGLE_CLOUD_PROJECT', + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', # If you need to use a specific version of pip, # change pip_version_override to the string representation @@ -57,13 +55,13 @@ "pip_version_override": None, # A dictionary you want to inject into your test. Don't put any # secrets here. These values will override predefined values. - 'envs': {}, + "envs": {}, } try: # Ensure we can import noxfile_config in the project's directory. - sys.path.append('.') + sys.path.append(".") from noxfile_config import TEST_CONFIG_OVERRIDE except ImportError as e: print("No user noxfile_config found: detail: {}".format(e)) @@ -78,12 +76,12 @@ def get_pytest_env_vars() -> Dict[str, str]: ret = {} # Override the GCLOUD_PROJECT and the alias. - env_key = TEST_CONFIG['gcloud_project_env'] + env_key = TEST_CONFIG["gcloud_project_env"] # This should error out if not set. - ret['GOOGLE_CLOUD_PROJECT'] = os.environ[env_key] + ret["GOOGLE_CLOUD_PROJECT"] = os.environ[env_key] # Apply user supplied envs. - ret.update(TEST_CONFIG['envs']) + ret.update(TEST_CONFIG["envs"]) return ret @@ -92,11 +90,14 @@ def get_pytest_env_vars() -> Dict[str, str]: ALL_VERSIONS = ["3.6", "3.7", "3.8", "3.9"] # Any default versions that should be ignored. -IGNORED_VERSIONS = TEST_CONFIG['ignored_versions'] +IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS]) -INSTALL_LIBRARY_FROM_SOURCE = os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False) in ("True", "true") +INSTALL_LIBRARY_FROM_SOURCE = os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False) in ( + "True", + "true", +) # # Style Checks # @@ -141,7 +142,7 @@ def _determine_local_import_names(start_dir: str) -> List[str]: @nox.session def lint(session: nox.sessions.Session) -> None: - if not TEST_CONFIG['enforce_type_hints']: + if not TEST_CONFIG["enforce_type_hints"]: session.install("flake8", "flake8-import-order") else: session.install("flake8", "flake8-import-order", "flake8-annotations") @@ -150,9 +151,11 @@ def lint(session: nox.sessions.Session) -> None: args = FLAKE8_COMMON_ARGS + [ "--application-import-names", ",".join(local_names), - "." + ".", ] session.run("flake8", *args) + + # # Black # @@ -165,6 +168,7 @@ def blacken(session: nox.sessions.Session) -> None: session.run("black", *python_files) + # # Sample Tests # @@ -173,7 +177,9 @@ def blacken(session: nox.sessions.Session) -> None: PYTEST_COMMON_ARGS = ["--junitxml=sponge_log.xml"] -def _session_tests(session: nox.sessions.Session, post_install: Callable = None) -> None: +def _session_tests( + session: nox.sessions.Session, post_install: Callable = None +) -> None: if TEST_CONFIG["pip_version_override"]: pip_version = TEST_CONFIG["pip_version_override"] session.install(f"pip=={pip_version}") @@ -203,7 +209,7 @@ def _session_tests(session: nox.sessions.Session, post_install: Callable = None) # on travis where slow and flaky tests are excluded. # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html success_codes=[0, 5], - env=get_pytest_env_vars() + env=get_pytest_env_vars(), ) @@ -213,9 +219,9 @@ def py(session: nox.sessions.Session) -> None: if session.python in TESTED_VERSIONS: _session_tests(session) else: - session.skip("SKIPPED: {} tests are disabled for this sample.".format( - session.python - )) + session.skip( + "SKIPPED: {} tests are disabled for this sample.".format(session.python) + ) # From dc44b6a596095b4454009209f1c9c10d3c4d40a7 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 17 Sep 2021 17:48:09 +0200 Subject: [PATCH 025/113] chore(deps): update dependency google-cloud-storage to v1.42.2 (#120) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [google-cloud-storage](https://togithub.com/googleapis/python-storage) | `==1.42.1` -> `==1.42.2` | [![age](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.42.2/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.42.2/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.42.2/compatibility-slim/1.42.1)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/1.42.2/confidence-slim/1.42.1)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
googleapis/python-storage ### [`v1.42.2`](https://togithub.com/googleapis/python-storage/blob/master/CHANGELOG.md#​1422-httpswwwgithubcomgoogleapispython-storagecomparev1421v1422-2021-09-16) [Compare Source](https://togithub.com/googleapis/python-storage/compare/v1.42.1...v1.42.2)
--- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/python-compute). --- compute/compute/snippets/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index 394c07aa632..cec6ce6e2ee 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,2 +1,2 @@ pytest==6.2.5 -google-cloud-storage==1.42.1 \ No newline at end of file +google-cloud-storage==1.42.2 \ No newline at end of file From f9b4818790687b4ebecc20bc613cb85fb6a925ed Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Fri, 24 Sep 2021 14:34:02 +0200 Subject: [PATCH 026/113] chore(docs): Adding firewall samples. (#117) Co-authored-by: Dina Graves Portman Co-authored-by: Remigiusz Samborski --- compute/compute/snippets/sample_firewall.py | 175 ++++++++++++++++++ .../compute/snippets/test_sample_firewall.py | 71 +++++++ 2 files changed, 246 insertions(+) create mode 100644 compute/compute/snippets/sample_firewall.py create mode 100644 compute/compute/snippets/test_sample_firewall.py diff --git a/compute/compute/snippets/sample_firewall.py b/compute/compute/snippets/sample_firewall.py new file mode 100644 index 00000000000..01a60c3d8e2 --- /dev/null +++ b/compute/compute/snippets/sample_firewall.py @@ -0,0 +1,175 @@ +# 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 typing import Iterable + +# [START compute_firewall_list] +# [START compute_firewall_create] +# [START compute_firewall_patch] +# [START compute_firewall_delete] +import google.cloud.compute_v1 as compute_v1 +# [END compute_firewall_delete] +# [END compute_firewall_patch] +# [END compute_firewall_create] +# [END compute_firewall_list] + + +# [START compute_firewall_list] +def list_firewall_rules(project_id: str) -> Iterable: + """ + 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] + + +def print_firewall_rule(project_id: str, firewall_rule_name: str): + firewall_client = compute_v1.FirewallsClient() + print(firewall_client.get(project=project_id, firewall=firewall_rule_name)) + + +# [START compute_firewall_create] +def create_firewall_rule( + project_id: str, firewall_rule_name: str, network: str = "global/networks/default" +): + """ + 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} + """ + firewall_rule = compute_v1.Firewall() + firewall_rule.name = firewall_rule_name + firewall_rule.direction = compute_v1.Firewall.Direction.INGRESS + + tcp_80_443_allowed = compute_v1.Allowed() + tcp_80_443_allowed.I_p_protocol = "tcp" + tcp_80_443_allowed.ports = ["80", "443"] + + firewall_rule.allowed = [tcp_80_443_allowed] + 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." + + # 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: + + # firewall_rule.priority = 0 + + firewall_client = compute_v1.FirewallsClient() + op = firewall_client.insert(project=project_id, firewall_resource=firewall_rule) + + op_client = compute_v1.GlobalOperationsClient() + op_client.wait(project=project_id, operation=op.name) + + return +# [END compute_firewall_create] + + +# [START compute_firewall_patch] +def patch_firewall_priority(project_id: str, firewall_rule_name: str, priority: int): + """ + 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 + ) + + operation_client = compute_v1.GlobalOperationsClient() + operation_client.wait(project=project_id, operation=operation.name) + return +# [END compute_firewall_patch] + + +# [START compute_firewall_delete] +def delete_firewall_rule(project_id: str, firewall_rule_name: str): + """ + Deleted 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) + + operation_client = compute_v1.GlobalOperationsClient() + operation_client.wait(project=project_id, operation=operation.name) + return +# [END compute_firewall_delete] + + +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_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_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/compute/snippets/test_sample_firewall.py b/compute/compute/snippets/test_sample_firewall.py new file mode 100644 index 00000000000..4e738464854 --- /dev/null +++ b/compute/compute/snippets/test_sample_firewall.py @@ -0,0 +1,71 @@ +# 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.auth +from google.cloud import compute_v1 +import pytest + + +from sample_firewall import ( + create_firewall_rule, + delete_firewall_rule, + list_firewall_rules, + 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 = compute_v1.Firewall.Direction.INGRESS + tcp_80_443_allowed = compute_v1.Allowed() + tcp_80_443_allowed.I_p_protocol = "tcp" + tcp_80_443_allowed.ports = ["80"] + firewall_rule.allowed = [tcp_80_443_allowed] + 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_client = compute_v1.FirewallsClient() + op = firewall_client.insert(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) + + op = firewall_client.delete(project=PROJECT, firewall=firewall_rule.name) + op_client.wait(project=PROJECT, operation=op.name) + + +def test_create_delete(): + rule_name = "firewall-sample-test-" + uuid.uuid4().hex[:10] + create_firewall_rule(PROJECT, rule_name) + assert any(rule.name == rule_name for rule in list_firewall_rules(PROJECT)) + delete_firewall_rule(PROJECT, rule_name) + assert all(rule.name != rule_name for rule in list_firewall_rules(PROJECT)) + + +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 From cbf749bfac1e0c90c4083e1476720cf4b59a5aac Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 1 Oct 2021 12:21:09 +0200 Subject: [PATCH 027/113] chore(deps): update dependency google-cloud-storage to v1.42.3 (#122) --- compute/compute/snippets/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index cec6ce6e2ee..db0c327448c 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,2 +1,2 @@ pytest==6.2.5 -google-cloud-storage==1.42.2 \ No newline at end of file +google-cloud-storage==1.42.3 \ No newline at end of file From 6135efce140f340e28a7a6c8b90b700395fee228 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Thu, 14 Oct 2021 04:21:41 -0700 Subject: [PATCH 028/113] changes without context (#123) autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/noxfile.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compute/compute/snippets/noxfile.py b/compute/compute/snippets/noxfile.py index b008613f03f..1fd8956fbf0 100644 --- a/compute/compute/snippets/noxfile.py +++ b/compute/compute/snippets/noxfile.py @@ -98,6 +98,10 @@ def get_pytest_env_vars() -> Dict[str, str]: "True", "true", ) + +# Error if a python version is missing +nox.options.error_on_missing_interpreters = True + # # Style Checks # From c4fb30a56d6d875c17f346fe166296edf77327a9 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 14 Oct 2021 12:32:05 -0400 Subject: [PATCH 029/113] feat: add support for python 3.10 (#131) * feat: add support for python 3.10 * add constraints files for python 3.10 and 3.11 --- compute/compute/snippets/noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/noxfile.py b/compute/compute/snippets/noxfile.py index 1fd8956fbf0..93a9122cc45 100644 --- a/compute/compute/snippets/noxfile.py +++ b/compute/compute/snippets/noxfile.py @@ -87,7 +87,7 @@ def get_pytest_env_vars() -> Dict[str, str]: # DO NOT EDIT - automatically generated. # All versions used to test samples. -ALL_VERSIONS = ["3.6", "3.7", "3.8", "3.9"] +ALL_VERSIONS = ["3.6", "3.7", "3.8", "3.9", "3.10"] # Any default versions that should be ignored. IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] From 6fa82a92fd139478bac07000ede96eec81637d33 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Tue, 19 Oct 2021 16:03:31 +0200 Subject: [PATCH 030/113] chore(docs): Adding samples for GCE instance creation (#130) * chore(docs): Adding samples for GCE instance creation Providing samples for the following docs page: https://cloud.google.com/compute/docs/instances/create-start-instance Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/sample_create_vm.py | 421 ++++++++++++++++++ compute/compute/snippets/sample_images.py | 59 +++ .../compute/snippets/test_sample_create_vm.py | 217 +++++++++ .../compute/snippets/test_sample_images.py | 33 ++ 4 files changed, 730 insertions(+) create mode 100644 compute/compute/snippets/sample_create_vm.py create mode 100644 compute/compute/snippets/sample_images.py create mode 100644 compute/compute/snippets/test_sample_create_vm.py create mode 100644 compute/compute/snippets/test_sample_images.py diff --git a/compute/compute/snippets/sample_create_vm.py b/compute/compute/snippets/sample_create_vm.py new file mode 100644 index 00000000000..24331f9d76b --- /dev/null +++ b/compute/compute/snippets/sample_create_vm.py @@ -0,0 +1,421 @@ +# 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 sys +from typing import List + +# [START compute_instances_create_with_subnet] +# [START compute_instances_create_from_image_plus_snapshot_disk] +# [START compute_instances_create_from_snapshot] +# [START compute_instances_create_from_image_plus_empty_disk] +# [START compute_instances_create_from_custom_image] +# [START compute_instances_create_from_image ] +from google.cloud import compute_v1 + + +# [END compute_instances_create_from_image ] +# [END compute_instances_create_from_custom_image] +# [END compute_instances_create_from_image_plus_empty_disk] +# [END compute_instances_create_from_snapshot] +# [END compute_instances_create_from_image_plus_snapshot_disk] +# [END compute_instances_create_with_subnet] + + +# [START compute_instances_create_with_subnet] +# [START compute_instances_create_from_image_plus_snapshot_disk] +# [START compute_instances_create_from_image_plus_empty_disk] +# [START compute_instances_create_from_custom_image] +# [START compute_instances_create_from_image] +def disk_from_image( + disk_type: str, disk_size_gb: int, boot: bool, source_image: str +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk + + +# [END compute_instances_create_from_image] +# [END compute_instances_create_from_custom_image] +# [END compute_instances_create_from_image_plus_empty_disk] +# [END compute_instances_create_from_image_plus_snapshot_disk] +# [END compute_instances_create_with_subnet] + + +# [START compute_instances_create_from_image_plus_empty_disk] +def empty_disk(disk_type: str, disk_size_gb: int) -> 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 + + 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 = True + disk.boot = False + return disk + + +# [END compute_instances_create_from_image_plus_empty_disk] + + +# [START compute_instances_create_from_image_plus_snapshot_disk] +# [START compute_instances_create_from_snapshot] +def disk_from_snapshot( + disk_type: str, disk_size_gb: int, boot: bool, disk_snapshot: str +) -> 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 + disk_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}" + + Returns: + AttachedDisk object configured to be created using the specified snapshot. + """ + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_snapshot = disk_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 = True + disk.boot = boot + return disk + + +# [END compute_instances_create_from_snapshot] +# [END compute_instances_create_from_image_plus_snapshot_disk] + + +# [START compute_instances_create_with_subnet] +# [START compute_instances_create_from_image_plus_snapshot_disk] +# [START compute_instances_create_from_snapshot] +# [START compute_instances_create_from_image_plus_empty_disk] +# [START compute_instances_create_from_custom_image] +# [START compute_instances_create_from_image] +def create_with_disks( + 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, +) -> 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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. + """ + instance_client = compute_v1.InstancesClient() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.name = instance_name + instance.disks = disks + full_machine_type_name = f"zones/{zone}/machineTypes/{machine_type}" + instance.machine_type = full_machine_type_name + instance.network_interfaces = [network_interface] + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +# [END compute_instances_create_from_image] +# [END compute_instances_create_from_custom_image] +# [END compute_instances_create_from_image_plus_empty_disk] +# [END compute_instances_create_from_snapshot] +# [END compute_instances_create_from_image_plus_snapshot_disk] +# [END compute_instances_create_with_subnet] + + +# [START compute_instances_create_from_image] +def create_from_public_image(project_id: str, zone: str, instance_name: str): + """ + 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. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_debian = image_client.get_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_with_disks(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_image] + + +# [START compute_instances_create_from_custom_image] +def create_from_custom_image( + project_id: str, zone: str, instance_name: str, custom_image_link: str +): + """ + 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)] + instance = create_with_disks(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_custom_image] + + +# [START compute_instances_create_from_image_plus_empty_disk] +def create_with_additional_disk(project_id: str, zone: str, instance_name: str): + """ + Create a new VM instance with Debian 10 operating system and a 11 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. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_debian = image_client.get_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), + empty_disk(disk_type, 11), + ] + instance = create_with_disks(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_image_plus_empty_disk] + + +# [START compute_instances_create_from_snapshot] +def create_from_snapshot( + project_id: str, zone: str, instance_name: str, snapshot_link: str +): + """ + 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. + 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, 11, True, snapshot_link)] + instance = create_with_disks(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_snapshot] + + +# [START compute_instances_create_from_image_plus_snapshot_disk] +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. + + 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. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_debian = image_client.get_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_with_disks(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_image_plus_snapshot_disk] + + +# [START compute_instances_create_with_subnet] +def create_with_subnet( + project_id: str, zone: str, instance_name: str, network_link: str, subnet_link: str +): + """ + 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. + """ + image_client = compute_v1.ImagesClient() + # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details + newest_debian = image_client.get_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_with_disks( + 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/compute/snippets/sample_images.py b/compute/compute/snippets/sample_images.py new file mode 100644 index 00000000000..9ea1ab45984 --- /dev/null +++ b/compute/compute/snippets/sample_images.py @@ -0,0 +1,59 @@ +# 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 typing import Iterable + +# [START compute_images_get] +# [START compute_images_list] +from google.cloud import compute_v1 + +# [END compute_images_list] +# [END compute_images_get] + + +# [START compute_images_list] +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_list] + + +# [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/compute/snippets/test_sample_create_vm.py b/compute/compute/snippets/test_sample_create_vm.py new file mode 100644 index 00000000000..b69570f162a --- /dev/null +++ b/compute/compute/snippets/test_sample_create_vm.py @@ -0,0 +1,217 @@ +# 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 quickstart import delete_instance, wait_for_operation +from sample_create_vm import ( + create_from_custom_image, + create_from_public_image, + create_from_snapshot, + create_with_additional_disk, + create_with_snapshotted_data_disk, + create_with_subnet, +) + +PROJECT = google.auth.default()[1] +INSTANCE_ZONE = "europe-central2-b" + + +def get_active_debian(): + image_client = compute_v1.ImagesClient() + + return image_client.get_from_family(project="debian-cloud", family="debian-11") + + +@pytest.fixture(scope="class") +def src_disk(request): + 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(project=PROJECT, zone=INSTANCE_ZONE, disk_resource=disk) + + wait_for_operation(op, PROJECT) + disk = disk_client.get(project=PROJECT, zone=INSTANCE_ZONE, disk=disk.name) + request.cls.disk = disk + + yield disk + + op = disk_client.delete(project=PROJECT, zone=INSTANCE_ZONE, disk=disk.name) + wait_for_operation(op, PROJECT) + + +@pytest.fixture(scope="class") +def snapshot(request, 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( + project=PROJECT, + zone=INSTANCE_ZONE, + disk=src_disk.name, + snapshot_resource=snapshot, + ) + wait_for_operation(op, PROJECT) + + request.cls.snapshot = snapshot_client.get(project=PROJECT, snapshot=snapshot.name) + snapshot = request.cls.snapshot + + yield snapshot + + op = snapshot_client.delete(project=PROJECT, snapshot=snapshot.name) + wait_for_operation(op, PROJECT) + + +@pytest.fixture(scope="class") +def image(request, 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(project=PROJECT, image_resource=image) + + wait_for_operation(op, PROJECT) + + image = image_client.get(project=PROJECT, image=image.name) + request.cls.image = image + yield image + + op = image_client.delete(project=PROJECT, image=image.name) + wait_for_operation(op, PROJECT) + + +@pytest.fixture() +def subnetwork(): + network_client = compute_v1.NetworksClient() + network = compute_v1.Network() + network.name = "test-network-" + uuid.uuid4().hex[:10] + network.auto_create_subnetworks = True + op = network_client.insert(project=PROJECT, network_resource=network) + wait_for_operation(op, PROJECT) + network = network_client.get(project=PROJECT, network=network.name) + + subnet = compute_v1.Subnetwork() + subnet.name = "test-subnet-" + uuid.uuid4().hex[:10] + subnet.network = network_client.get(project=PROJECT, network=network.name).self_link + subnet.region = "europe-central2" + subnet.ip_cidr_range = "10.0.0.0/20" + subnet_client = compute_v1.SubnetworksClient() + op = subnet_client.insert( + project=PROJECT, region="europe-central2", subnetwork_resource=subnet + ) + wait_for_operation(op, PROJECT) + subnet = subnet_client.get( + project=PROJECT, region="europe-central2", subnetwork=subnet.name + ) + + yield subnet + + op = subnet_client.delete(project=PROJECT, region='europe-central2', subnetwork=subnet.name) + wait_for_operation(op, PROJECT) + + op = network_client.delete(project=PROJECT, network=network.name) + wait_for_operation(op, PROJECT) + + +@pytest.mark.usefixtures("image", "snapshot") +class TestCreation: + def test_create_from_custom_image(self): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_from_custom_image( + PROJECT, INSTANCE_ZONE, instance_name, self.image.self_link + ) + try: + assert ( + instance.disks[0].initialize_params.source_image == self.image.self_link + ) + finally: + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + def test_create_from_public_image(self): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_from_public_image( + PROJECT, + INSTANCE_ZONE, + instance_name, + ) + try: + assert "debian-cloud" in instance.disks[0].initialize_params.source_image + assert "debian-10" in instance.disks[0].initialize_params.source_image + finally: + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + def test_create_from_snapshot(self): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_from_snapshot( + PROJECT, INSTANCE_ZONE, instance_name, self.snapshot.self_link + ) + try: + assert ( + instance.disks[0].initialize_params.source_snapshot + == self.snapshot.self_link + ) + finally: + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + def test_create_with_additional_disk(self): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_with_additional_disk(PROJECT, INSTANCE_ZONE, instance_name) + try: + assert any( + disk.initialize_params.disk_size_gb == 11 for disk in instance.disks + ) + assert any( + disk.initialize_params.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_snapshotted_data_disk(self): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_with_snapshotted_data_disk( + PROJECT, INSTANCE_ZONE, instance_name, self.snapshot.self_link + ) + try: + assert any( + disk.initialize_params.disk_size_gb == 11 for disk in instance.disks + ) + assert any( + disk.initialize_params.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(self, subnetwork): + instance_name = "i" + uuid.uuid4().hex[:10] + instance = create_with_subnet( + PROJECT, + INSTANCE_ZONE, + instance_name, + subnetwork.network, + subnetwork.self_link, + ) + try: + assert instance.network_interfaces[0].name == subnetwork.network + assert instance.network_interfaces[0].subnetwork == subnetwork.self_link + finally: + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) diff --git a/compute/compute/snippets/test_sample_images.py b/compute/compute/snippets/test_sample_images.py new file mode 100644 index 00000000000..23346c1f885 --- /dev/null +++ b/compute/compute/snippets/test_sample_images.py @@ -0,0 +1,33 @@ +# 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 sample_images import get_image, list_images + + +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 == image2 From 282ff6626b5eff0ced0f7791d8719be6046603c3 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 19 Oct 2021 16:51:05 +0200 Subject: [PATCH 031/113] chore(deps): update dependency google-cloud-compute to v0.6.0 (#135) Co-authored-by: Maciej Strzelczyk --- compute/compute/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements.txt b/compute/compute/snippets/requirements.txt index 209ab22d71a..aa24245a39b 100644 --- a/compute/compute/snippets/requirements.txt +++ b/compute/compute/snippets/requirements.txt @@ -1 +1 @@ -google-cloud-compute==0.5.0 \ No newline at end of file +google-cloud-compute==0.6.0 \ No newline at end of file From 9a26bfd7c02c746cf4970015260bbfd9beea9ad4 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 3 Nov 2021 16:05:07 +0100 Subject: [PATCH 032/113] chore(docs): Securing tests against leaks. (#139) * chore(docs): Securing tests against leaks. * chore(docs): Updating some docstrings. --- compute/compute/snippets/sample_create_vm.py | 4 +- .../compute/snippets/test_sample_create_vm.py | 92 +++++++------------ 2 files changed, 33 insertions(+), 63 deletions(-) diff --git a/compute/compute/snippets/sample_create_vm.py b/compute/compute/snippets/sample_create_vm.py index 24331f9d76b..21021e010df 100644 --- a/compute/compute/snippets/sample_create_vm.py +++ b/compute/compute/snippets/sample_create_vm.py @@ -324,7 +324,7 @@ def create_from_snapshot( project_id: str, zone: str, instance_name: str, snapshot_link: str ): """ - Create a new VM instance with Debian 10 operating system. + Create a new VM instance with boot disk created from a snapshot. Args: project_id: project ID or project number of the Cloud project you want to use. @@ -350,7 +350,7 @@ 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. + 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. diff --git a/compute/compute/snippets/test_sample_create_vm.py b/compute/compute/snippets/test_sample_create_vm.py index b69570f162a..d610d4776fe 100644 --- a/compute/compute/snippets/test_sample_create_vm.py +++ b/compute/compute/snippets/test_sample_create_vm.py @@ -17,8 +17,8 @@ from google.cloud import compute_v1 import pytest - from quickstart import delete_instance, wait_for_operation + from sample_create_vm import ( create_from_custom_image, create_from_public_image, @@ -29,7 +29,8 @@ ) PROJECT = google.auth.default()[1] -INSTANCE_ZONE = "europe-central2-b" +REGION = 'us-central1' +INSTANCE_ZONE = "us-central1-b" def get_active_debian(): @@ -48,13 +49,13 @@ def src_disk(request): op = disk_client.insert(project=PROJECT, zone=INSTANCE_ZONE, disk_resource=disk) wait_for_operation(op, PROJECT) - disk = disk_client.get(project=PROJECT, zone=INSTANCE_ZONE, disk=disk.name) - request.cls.disk = disk - - yield disk - - op = disk_client.delete(project=PROJECT, zone=INSTANCE_ZONE, disk=disk.name) - wait_for_operation(op, PROJECT) + try: + disk = disk_client.get(project=PROJECT, zone=INSTANCE_ZONE, disk=disk.name) + request.cls.disk = disk + yield disk + finally: + op = disk_client.delete(project=PROJECT, zone=INSTANCE_ZONE, disk=disk.name) + wait_for_operation(op, PROJECT) @pytest.fixture(scope="class") @@ -70,14 +71,16 @@ def snapshot(request, src_disk): snapshot_resource=snapshot, ) wait_for_operation(op, PROJECT) + try: + request.cls.snapshot = snapshot_client.get( + project=PROJECT, snapshot=snapshot.name + ) + snapshot = request.cls.snapshot - request.cls.snapshot = snapshot_client.get(project=PROJECT, snapshot=snapshot.name) - snapshot = request.cls.snapshot - - yield snapshot - - op = snapshot_client.delete(project=PROJECT, snapshot=snapshot.name) - wait_for_operation(op, PROJECT) + yield snapshot + finally: + op = snapshot_client.delete(project=PROJECT, snapshot=snapshot.name) + wait_for_operation(op, PROJECT) @pytest.fixture(scope="class") @@ -89,46 +92,13 @@ def image(request, src_disk): op = image_client.insert(project=PROJECT, image_resource=image) wait_for_operation(op, PROJECT) - - image = image_client.get(project=PROJECT, image=image.name) - request.cls.image = image - yield image - - op = image_client.delete(project=PROJECT, image=image.name) - wait_for_operation(op, PROJECT) - - -@pytest.fixture() -def subnetwork(): - network_client = compute_v1.NetworksClient() - network = compute_v1.Network() - network.name = "test-network-" + uuid.uuid4().hex[:10] - network.auto_create_subnetworks = True - op = network_client.insert(project=PROJECT, network_resource=network) - wait_for_operation(op, PROJECT) - network = network_client.get(project=PROJECT, network=network.name) - - subnet = compute_v1.Subnetwork() - subnet.name = "test-subnet-" + uuid.uuid4().hex[:10] - subnet.network = network_client.get(project=PROJECT, network=network.name).self_link - subnet.region = "europe-central2" - subnet.ip_cidr_range = "10.0.0.0/20" - subnet_client = compute_v1.SubnetworksClient() - op = subnet_client.insert( - project=PROJECT, region="europe-central2", subnetwork_resource=subnet - ) - wait_for_operation(op, PROJECT) - subnet = subnet_client.get( - project=PROJECT, region="europe-central2", subnetwork=subnet.name - ) - - yield subnet - - op = subnet_client.delete(project=PROJECT, region='europe-central2', subnetwork=subnet.name) - wait_for_operation(op, PROJECT) - - op = network_client.delete(project=PROJECT, network=network.name) - wait_for_operation(op, PROJECT) + try: + image = image_client.get(project=PROJECT, image=image.name) + request.cls.image = image + yield image + finally: + op = image_client.delete(project=PROJECT, image=image.name) + wait_for_operation(op, PROJECT) @pytest.mark.usefixtures("image", "snapshot") @@ -201,17 +171,17 @@ def test_create_with_snapshotted_data_disk(self): finally: delete_instance(PROJECT, INSTANCE_ZONE, instance_name) - def test_create_with_subnet(self, subnetwork): + def test_create_with_subnet(self): instance_name = "i" + uuid.uuid4().hex[:10] instance = create_with_subnet( PROJECT, INSTANCE_ZONE, instance_name, - subnetwork.network, - subnetwork.self_link, + "global/networks/default", + f"regions/{REGION}/subnetworks/default", ) try: - assert instance.network_interfaces[0].name == subnetwork.network - assert instance.network_interfaces[0].subnetwork == subnetwork.self_link + assert instance.network_interfaces[0].name == "global/networks/default" + assert instance.network_interfaces[0].subnetwork == f"regions/{REGION}/subnetworks/default" finally: delete_instance(PROJECT, INSTANCE_ZONE, instance_name) From 1a41815fcc100604415c9ee253a71d23c4c1408a Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Mon, 8 Nov 2021 12:49:18 +0100 Subject: [PATCH 033/113] chore(docs): Updating firewall samples. (#134) Applying suggested changes. Closes #133 Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/sample_firewall.py | 18 ++++++++++-------- .../compute/snippets/test_sample_firewall.py | 14 +++++++++----- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/compute/compute/snippets/sample_firewall.py b/compute/compute/snippets/sample_firewall.py index 01a60c3d8e2..1454932a38c 100644 --- a/compute/compute/snippets/sample_firewall.py +++ b/compute/compute/snippets/sample_firewall.py @@ -48,9 +48,9 @@ def list_firewall_rules(project_id: str) -> Iterable: # [END compute_firewall_list] -def print_firewall_rule(project_id: str, firewall_rule_name: str): +def get_firewall_rule(project_id: str, firewall_rule_name: str) -> compute_v1.Firewall: firewall_client = compute_v1.FirewallsClient() - print(firewall_client.get(project=project_id, firewall=firewall_rule_name)) + return firewall_client.get(project=project_id, firewall=firewall_rule_name) # [START compute_firewall_create] @@ -72,15 +72,17 @@ def create_firewall_rule( firewall_rule.name = firewall_rule_name firewall_rule.direction = compute_v1.Firewall.Direction.INGRESS - tcp_80_443_allowed = compute_v1.Allowed() - tcp_80_443_allowed.I_p_protocol = "tcp" - tcp_80_443_allowed.ports = ["80", "443"] + allowed_ports = compute_v1.Allowed() + allowed_ports.I_p_protocol = "tcp" + allowed_ports.ports = ["80", "443"] - firewall_rule.allowed = [tcp_80_443_allowed] + 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 @@ -164,11 +166,11 @@ def delete_firewall_rule(project_id: str, firewall_rule_name: str): create_firewall_rule(default_project_id, rule_name) try: print("Rule created:") - print_firewall_rule(default_project_id, rule_name) + 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_firewall_rule(default_project_id, rule_name) + print(get_firewall_rule(default_project_id, rule_name)) print(f"Deleting rule {rule_name}...") finally: delete_firewall_rule(default_project_id, rule_name) diff --git a/compute/compute/snippets/test_sample_firewall.py b/compute/compute/snippets/test_sample_firewall.py index 4e738464854..9f99bfdbfbb 100644 --- a/compute/compute/snippets/test_sample_firewall.py +++ b/compute/compute/snippets/test_sample_firewall.py @@ -22,6 +22,7 @@ from sample_firewall import ( create_firewall_rule, delete_firewall_rule, + get_firewall_rule, list_firewall_rules, patch_firewall_priority, ) @@ -34,13 +35,14 @@ def firewall_rule(): firewall_rule = compute_v1.Firewall() firewall_rule.name = "firewall-sample-test" + uuid.uuid4().hex[:10] firewall_rule.direction = compute_v1.Firewall.Direction.INGRESS - tcp_80_443_allowed = compute_v1.Allowed() - tcp_80_443_allowed.I_p_protocol = "tcp" - tcp_80_443_allowed.ports = ["80"] - firewall_rule.allowed = [tcp_80_443_allowed] + 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(project=PROJECT, firewall_resource=firewall_rule) @@ -57,7 +59,9 @@ def firewall_rule(): def test_create_delete(): rule_name = "firewall-sample-test-" + uuid.uuid4().hex[:10] create_firewall_rule(PROJECT, rule_name) - assert any(rule.name == rule_name for rule in list_firewall_rules(PROJECT)) + rule = get_firewall_rule(PROJECT, rule_name) + assert rule.name == rule_name + assert 'web' in rule.target_tags delete_firewall_rule(PROJECT, rule_name) assert all(rule.name != rule_name for rule in list_firewall_rules(PROJECT)) From 683155da8da74c00dfd9878a26455d709944f96a Mon Sep 17 00:00:00 2001 From: Fedor Isakov Date: Tue, 9 Nov 2021 12:37:52 +0300 Subject: [PATCH 034/113] chore(samples): fix region tag import (#142) --- compute/compute/snippets/sample_start_stop.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/compute/compute/snippets/sample_start_stop.py b/compute/compute/snippets/sample_start_stop.py index d5bee8f0544..6f3c87e42cc 100644 --- a/compute/compute/snippets/sample_start_stop.py +++ b/compute/compute/snippets/sample_start_stop.py @@ -17,8 +17,18 @@ """ A sample script showing how to start and stop Google Compute Engine instances. """ + +# [START compute_start_instance] +# [START compute_start_enc_instance] +# [START compute_stop_instance] +# [START compute_reset_instance] from google.cloud import compute_v1 +# [END compute_reset_instance] +# [END compute_stop_instance] +# [END compute_start_enc_instance] +# [END compute_start_instance] + # [START compute_start_instance] def start_instance(project_id: str, zone: str, instance_name: str): From 27d95f169b78634908ba1412ee2b66addc41f19d Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 9 Nov 2021 18:08:45 +0100 Subject: [PATCH 035/113] chore(deps): update dependency google-cloud-compute to v0.7.0 (#148) --- compute/compute/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements.txt b/compute/compute/snippets/requirements.txt index aa24245a39b..ba4ff548386 100644 --- a/compute/compute/snippets/requirements.txt +++ b/compute/compute/snippets/requirements.txt @@ -1 +1 @@ -google-cloud-compute==0.6.0 \ No newline at end of file +google-cloud-compute==0.7.0 \ No newline at end of file From 900b485ecdafacdc3318a6d949621b0f0f174316 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 16 Nov 2021 11:38:20 -0500 Subject: [PATCH 036/113] feat: update to the latest version of the code generator (#147) The compute protos are now [available in googleapis/googleapis](https://github.com/googleapis/googleapis/tree/master/google/cloud/compute/v1), and the generated code is also available in googleapis/googleapis-gen [here](https://github.com/googleapis/googleapis-gen/tree/master/google/cloud/compute/v1/compute-small-v1-py). This PR pulls in the latest version of the generated client from googleapis-gen, which may have significant differences. I've added the `do not merge` label while we discuss whether any differences could break user code. This PR also deletes `synth.py` and adds configuration files for owl bot. owl bot will save time for maintainers as it will automatically open PRs when there are updates in googleapis-gen without requiring maintainers to run synthtool to build the client from protos. Additionally, similar to autosynth, PRs will be automatically opened when there are updates to templated files. fix(deps): require google-api-core >=2.2.0 fix(deps): require proto-plus >=1.19.7 docs: list oneofs in docstring fix: add 'dict' annotation type to 'request' feat: support self-signed JWT flow for service accounts chore(samples): Merging usage bucket tests into one, to avoid race conditions --- compute/compute/snippets/sample_default_values.py | 6 +++--- compute/compute/snippets/test_sample_default_values.py | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/compute/compute/snippets/sample_default_values.py b/compute/compute/snippets/sample_default_values.py index 53dbaf62d0a..40f07ea1d71 100644 --- a/compute/compute/snippets/sample_default_values.py +++ b/compute/compute/snippets/sample_default_values.py @@ -112,10 +112,10 @@ def disable_usage_export(project_id: str) -> None: """ projects_client = compute_v1.ProjectsClient() - # Updating the setting with None will disable the - # usage report generation. + # 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=None) + project=project_id, usage_export_location_resource={}) op_client = compute_v1.GlobalOperationsClient() diff --git a/compute/compute/snippets/test_sample_default_values.py b/compute/compute/snippets/test_sample_default_values.py index 613c6efa39f..16216101c24 100644 --- a/compute/compute/snippets/test_sample_default_values.py +++ b/compute/compute/snippets/test_sample_default_values.py @@ -51,9 +51,8 @@ def test_set_usage_export_bucket_default(capsys: typing.Any, assert(uel.bucket_name == '') assert(uel.report_name_prefix == '') - -def test_set_usage_export_bucket_custom(capsys: typing.Any, - temp_bucket: storage.Bucket) -> None: + # 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 From e95ba15f5a60beb4d78be919a8ee5b710c783504 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Wed, 17 Nov 2021 11:42:41 +0100 Subject: [PATCH 037/113] chore(deps): update dependency google-cloud-compute to v0.8.0 (#154) Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements.txt b/compute/compute/snippets/requirements.txt index ba4ff548386..3d8cb4e6cb2 100644 --- a/compute/compute/snippets/requirements.txt +++ b/compute/compute/snippets/requirements.txt @@ -1 +1 @@ -google-cloud-compute==0.7.0 \ No newline at end of file +google-cloud-compute==0.8.0 \ No newline at end of file From f2bbb5b2725a50198dad846376f29b3ac3888c0a Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 17 Nov 2021 16:22:08 +0100 Subject: [PATCH 038/113] docs(samples): Adding template samples (#136) Co-authored-by: Kurtis Van Gent <31518063+kurtisvg@users.noreply.github.com> Co-authored-by: Kurtis Van Gent <31518063+kurtisvg@users.noreply.github.com> --- compute/compute/snippets/sample_templates.py | 229 ++++++++++++++++++ .../snippets/test_sample_start_stop.py | 3 +- .../compute/snippets/test_sample_templates.py | 108 +++++++++ 3 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 compute/compute/snippets/sample_templates.py create mode 100644 compute/compute/snippets/test_sample_templates.py diff --git a/compute/compute/snippets/sample_templates.py b/compute/compute/snippets/sample_templates.py new file mode 100644 index 00000000000..99bf5bdd5b6 --- /dev/null +++ b/compute/compute/snippets/sample_templates.py @@ -0,0 +1,229 @@ +# 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. + +# [START compute_template_list ] +from typing import Iterable +# [END compute_template_list ] + +# [START compute_template_create ] +# [START compute_template_list ] +# [START compute_template_get ] +# [START compute_template_create_from_instance ] +# [START compute_template_create_with_subnet ] +# [START compute_template_delete ] +from google.cloud import compute_v1 +# [END compute_template_delete ] +# [END compute_template_create_with_subnet ] +# [END compute_template_create_from_instance ] +# [END compute_template_get ] +# [END compute_template_list ] +# [END compute_template_create ] + + +# [START compute_template_get ] +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 ] + + +# [START compute_template_list ] +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 ] + + +# [START compute_template_create ] +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_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT + access_config.network_tier = ( + compute_v1.AccessConfig.NetworkTier.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_client = compute_v1.GlobalOperationsClient() + op = template_client.insert(project=project_id, instance_template_resource=template) + operation_client.wait(project=project_id, operation=op.name) + + return template_client.get(project=project_id, instance_template=template_name) +# [END compute_template_create ] + + +# [START compute_template_create_from_instance ] +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 = ( + compute_v1.DiskInstantiationConfig.InstantiateFrom.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_client = compute_v1.GlobalOperationsClient() + op = template_client.insert(project=project_id, instance_template_resource=template) + operation_client.wait(project=project_id, operation=op.name) + + return template_client.get(project=project_id, instance_template=template_name) +# [END compute_template_create_from_instance ] + + +# [START compute_template_create_with_subnet ] +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_client = compute_v1.GlobalOperationsClient() + op = template_client.insert(project=project_id, instance_template_resource=template) + operation_client.wait(project=project_id, operation=op.name) + + return template_client.get(project=project_id, instance_template=template_name) +# [END compute_template_create_with_subnet ] + + +# [START compute_template_delete ] +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_client = compute_v1.GlobalOperationsClient() + op = template_client.delete(project=project_id, instance_template=template_name) + operation_client.wait(project=project_id, operation=op.name) + return +# [END compute_template_delete ] diff --git a/compute/compute/snippets/test_sample_start_stop.py b/compute/compute/snippets/test_sample_start_stop.py index a2f2bf37753..2d6aac5c278 100644 --- a/compute/compute/snippets/test_sample_start_stop.py +++ b/compute/compute/snippets/test_sample_start_stop.py @@ -22,7 +22,7 @@ import pytest -from samples.snippets.sample_start_stop import start_instance, start_instance_with_encryption_key, stop_instance +from sample_start_stop import start_instance, start_instance_with_encryption_key, stop_instance PROJECT = google.auth.default()[1] @@ -43,6 +43,7 @@ def _make_disk(raw_key: bytes = None): disk.auto_delete = True disk.boot = True disk.type_ = compute_v1.AttachedDisk.Type.PERSISTENT + disk.device_name = 'disk-1' if raw_key: disk.disk_encryption_key = compute_v1.CustomerEncryptionKey() diff --git a/compute/compute/snippets/test_sample_templates.py b/compute/compute/snippets/test_sample_templates.py new file mode 100644 index 00000000000..2c60aaafb04 --- /dev/null +++ b/compute/compute/snippets/test_sample_templates.py @@ -0,0 +1,108 @@ +# 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 sample_templates import ( + create_template, + create_template_from_instance, + create_template_with_subnet, + delete_instance_template, + list_instance_templates, +) + +# Turning off F401 check because flake8 doesn't recognize using +# PyTest fixture as parameter as usage. +from test_sample_start_stop import compute_instance # noqa: F401 + +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) + ) From aa4304760b8725e71d57b02ec0bc3f8f62db7e59 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Thu, 18 Nov 2021 11:11:11 +0100 Subject: [PATCH 039/113] chore(deps): update dependency google-cloud-storage to v1.43.0 (#157) --- compute/compute/snippets/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index db0c327448c..19762f901f5 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,2 +1,2 @@ pytest==6.2.5 -google-cloud-storage==1.42.3 \ No newline at end of file +google-cloud-storage==1.43.0 \ No newline at end of file From 1253c818d6e1ec16bb605d37cb471e8a8f1c44d6 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 24 Nov 2021 13:50:31 +0100 Subject: [PATCH 040/113] docs(samples): Adding samples for instance from template creation (#159) * docs(samples): Adding samples for instance from template creation --- .../snippets/sample_instance_from_template.py | 135 ++++++++++++++++++ .../test_sample_instance_from_template.py | 98 +++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 compute/compute/snippets/sample_instance_from_template.py create mode 100644 compute/compute/snippets/test_sample_instance_from_template.py diff --git a/compute/compute/snippets/sample_instance_from_template.py b/compute/compute/snippets/sample_instance_from_template.py new file mode 100644 index 00000000000..ab3f52be74d --- /dev/null +++ b/compute/compute/snippets/sample_instance_from_template.py @@ -0,0 +1,135 @@ +# 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. + +# [START compute_instances_create_from_template] +# [START compute_instances_create_from_template_with_overrides] +from google.cloud import compute_v1 +# [END compute_instances_create_from_template_with_overrides] + + +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. + """ + operation_client = compute_v1.ZoneOperationsClient() + 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 + + op = instance_client.insert(instance_insert_request) + operation_client.wait(project=project_id, zone=zone, operation=op.name) + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +# [END compute_instances_create_from_template] + + +# [START compute_instances_create_from_template_with_overrides] +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 + newDiskSourceImage: 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. + """ + operation_client = compute_v1.ZoneOperationsClient() + 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 = 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_ = compute_v1.AttachedDisk.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 + + op = instance_client.insert(instance_insert_request) + operation_client.wait(project=project_id, zone=zone, operation=op.name) + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +# [END compute_instances_create_from_template_with_overrides] diff --git a/compute/compute/snippets/test_sample_instance_from_template.py b/compute/compute/snippets/test_sample_instance_from_template.py new file mode 100644 index 00000000000..5728c2c476a --- /dev/null +++ b/compute/compute/snippets/test_sample_instance_from_template.py @@ -0,0 +1,98 @@ +# 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 quickstart import delete_instance +from sample_instance_from_template import ( + create_instance_from_template, + create_instance_from_template_with_overrides, +) + + +PROJECT = google.auth.default()[1] +INSTANCE_ZONE = "us-central1-b" + + +@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 = "e2-standard-4" + template.properties.network_interfaces = [network_interface] + + template_client = compute_v1.InstanceTemplatesClient() + operation_client = compute_v1.GlobalOperationsClient() + op = template_client.insert(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(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="centos-cloud", family="centos-8") + 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 From d0ad6ae9410dfebf33f3fb7ef79a49d39dc2d5d5 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 29 Nov 2021 06:47:32 -0500 Subject: [PATCH 041/113] feat: Switch to string enums for compute (#685) (#158) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Switch to string enums for compute (#685) Also introduce gapic_yaml for java clients to override default LRO configuration (which has too way too long timeouts). Also cleanup regenerate other files (grpc_service_config and service yaml) Source-Link: https://github.com/googleapis/googleapis/commit/8ce4ea696af254610679c43e7f8c16aff0b9f4e7 Source-Link: https://github.com/googleapis/googleapis-gen/commit/55e242c7cf5e600fabb8d767bd06f4fdfad6a015 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiNTVlMjQyYzdjZjVlNjAwZmFiYjhkNzY3YmQwNmY0ZmRmYWQ2YTAxNSJ9 * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * run blacken on all directories with a noxfile * chore: Update integration tests to reflect API changes * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore: Update integration test to reflect API changes * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/noxfile_config.py | 2 +- compute/compute/snippets/quickstart.py | 16 ++++++- .../compute/snippets/sample_default_values.py | 42 +++++++++------- compute/compute/snippets/sample_firewall.py | 13 ++++- .../snippets/sample_instance_from_template.py | 3 +- compute/compute/snippets/sample_pagination.py | 25 ++++++---- compute/compute/snippets/sample_start_stop.py | 40 +++++++++------- compute/compute/snippets/sample_templates.py | 32 +++++++++---- .../compute/snippets/test_sample_create_vm.py | 13 +++-- .../snippets/test_sample_default_values.py | 39 ++++++++------- .../compute/snippets/test_sample_firewall.py | 6 +-- .../snippets/test_sample_pagination.py | 6 +-- .../snippets/test_sample_start_stop.py | 48 ++++++++++++------- 13 files changed, 182 insertions(+), 103 deletions(-) diff --git a/compute/compute/snippets/noxfile_config.py b/compute/compute/snippets/noxfile_config.py index 16c4c694efc..5794d5fe1ed 100644 --- a/compute/compute/snippets/noxfile_config.py +++ b/compute/compute/snippets/noxfile_config.py @@ -14,5 +14,5 @@ TEST_CONFIG_OVERRIDE = { # Tests in test_sample_default_values.py require separate projects to not interfere with each other. - 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + "gcloud_project_env": "BUILD_SPECIFIC_GCLOUD_PROJECT", } diff --git a/compute/compute/snippets/quickstart.py b/compute/compute/snippets/quickstart.py index 74226cda3f0..fa481f8c9be 100644 --- a/compute/compute/snippets/quickstart.py +++ b/compute/compute/snippets/quickstart.py @@ -56,6 +56,8 @@ def list_instances(project_id: str, zone: str) -> typing.Iterable[compute_v1.Ins print(f" - {instance.name} ({instance.machine_type})") return instance_list + + # [END compute_instances_list] @@ -74,7 +76,9 @@ def list_all_instances( """ instance_client = compute_v1.InstancesClient() # Use the `max_results` parameter to limit the number of results that the API returns per response page. - request = compute_v1.AggregatedListInstancesRequest(project=project_id, max_results=5) + request = compute_v1.AggregatedListInstancesRequest( + project=project_id, max_results=5 + ) agg_list = instance_client.aggregated_list(request=request) all_instances = {} print("Instances found:") @@ -88,6 +92,8 @@ def list_all_instances( for instance in response.instances: print(f" - {instance.name} ({instance.machine_type})") return all_instances + + # [END compute_instances_list_all] @@ -133,7 +139,7 @@ def create_instance( disk.initialize_params = initialize_params disk.auto_delete = True disk.boot = True - disk.type_ = compute_v1.AttachedDisk.Type.PERSISTENT + disk.type_ = "PERSISTENT" # Use the network interface provided in the network_name argument. network_interface = compute_v1.NetworkInterface() @@ -166,6 +172,8 @@ def create_instance( print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") return instance + + # [END compute_instances_create] @@ -196,6 +204,8 @@ def delete_instance(project_id: str, zone: str, machine_name: str) -> None: print("Warning during deletion:", operation.warnings, file=sys.stderr) print(f"Instance {machine_name} deleted.") return + + # [END compute_instances_delete] @@ -227,6 +237,8 @@ def wait_for_operation( else: client = compute_v1.GlobalOperationsClient() return client.wait(**kwargs) + + # [END compute_instances_operation_check] diff --git a/compute/compute/snippets/sample_default_values.py b/compute/compute/snippets/sample_default_values.py index 40f07ea1d71..705a81d0b7b 100644 --- a/compute/compute/snippets/sample_default_values.py +++ b/compute/compute/snippets/sample_default_values.py @@ -22,14 +22,16 @@ # [START compute_usage_report_get] # [START compute_usage_report_disable] 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 set_usage_export_bucket(project_id: str, bucket_name: str, - report_name_prefix: str = "") -> None: +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 @@ -43,27 +45,28 @@ def set_usage_export_bucket(project_id: str, bucket_name: str, to showcase default values behaviour. """ usage_export_location = compute_v1.UsageExportLocation( - bucket_name=bucket_name, - report_name_prefix=report_name_prefix + bucket_name=bucket_name, 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`.") + 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) + project=project_id, usage_export_location_resource=usage_export_location + ) op_client = compute_v1.GlobalOperationsClient() while operation.status != compute_v1.Operation.Status.DONE: - operation = op_client.wait( - operation=operation.name, project=project_id - ) + operation = op_client.wait(operation=operation.name, project=project_id) + # [END compute_usage_report_set] @@ -94,10 +97,14 @@ def get_usage_export_bucket(project_id: str) -> compute_v1.UsageExportLocation: # 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' + 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] @@ -115,12 +122,13 @@ def disable_usage_export(project_id: str) -> None: # 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={}) + project=project_id, usage_export_location_resource={} + ) op_client = compute_v1.GlobalOperationsClient() while operation.status != compute_v1.Operation.Status.DONE: - operation = op_client.wait( - operation=operation.name, project=project_id - ) + operation = op_client.wait(operation=operation.name, project=project_id) + + # [END compute_usage_report_disable] diff --git a/compute/compute/snippets/sample_firewall.py b/compute/compute/snippets/sample_firewall.py index 1454932a38c..b9a0d3d2f99 100644 --- a/compute/compute/snippets/sample_firewall.py +++ b/compute/compute/snippets/sample_firewall.py @@ -20,6 +20,7 @@ # [START compute_firewall_patch] # [START compute_firewall_delete] import google.cloud.compute_v1 as compute_v1 + # [END compute_firewall_delete] # [END compute_firewall_patch] # [END compute_firewall_create] @@ -45,6 +46,8 @@ def list_firewall_rules(project_id: str) -> Iterable: print(f" - {firewall.name}: {firewall.description}") return firewalls_list + + # [END compute_firewall_list] @@ -70,7 +73,7 @@ def create_firewall_rule( """ firewall_rule = compute_v1.Firewall() firewall_rule.name = firewall_rule_name - firewall_rule.direction = compute_v1.Firewall.Direction.INGRESS + firewall_rule.direction = "INGRESS" allowed_ports = compute_v1.Allowed() allowed_ports.I_p_protocol = "tcp" @@ -81,7 +84,7 @@ def create_firewall_rule( firewall_rule.network = network firewall_rule.description = "Allowing TCP traffic on port 80 and 443 from Internet." - firewall_rule.target_tags = ['web'] + 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 @@ -98,6 +101,8 @@ def create_firewall_rule( op_client.wait(project=project_id, operation=op.name) return + + # [END compute_firewall_create] @@ -124,6 +129,8 @@ def patch_firewall_priority(project_id: str, firewall_rule_name: str, priority: operation_client = compute_v1.GlobalOperationsClient() operation_client.wait(project=project_id, operation=operation.name) return + + # [END compute_firewall_patch] @@ -142,6 +149,8 @@ def delete_firewall_rule(project_id: str, firewall_rule_name: str): operation_client = compute_v1.GlobalOperationsClient() operation_client.wait(project=project_id, operation=operation.name) return + + # [END compute_firewall_delete] diff --git a/compute/compute/snippets/sample_instance_from_template.py b/compute/compute/snippets/sample_instance_from_template.py index ab3f52be74d..5c3e03935c6 100644 --- a/compute/compute/snippets/sample_instance_from_template.py +++ b/compute/compute/snippets/sample_instance_from_template.py @@ -15,6 +15,7 @@ # [START compute_instances_create_from_template] # [START compute_instances_create_from_template_with_overrides] from google.cloud import compute_v1 + # [END compute_instances_create_from_template_with_overrides] @@ -116,7 +117,7 @@ def create_instance_from_template_with_overrides( new_disk.initialize_params.source_image = new_disk_source_image new_disk.auto_delete = True new_disk.boot = False - new_disk.type_ = compute_v1.AttachedDisk.Type.PERSISTENT + new_disk.type_ = "PERSISTENT" instance.disks.append(new_disk) diff --git a/compute/compute/snippets/sample_pagination.py b/compute/compute/snippets/sample_pagination.py index 2782a3c3ae3..e2590b54160 100644 --- a/compute/compute/snippets/sample_pagination.py +++ b/compute/compute/snippets/sample_pagination.py @@ -17,6 +17,7 @@ # [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] @@ -34,14 +35,17 @@ def print_images_list(project: str) -> None: """ 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") + images_list_request = compute_v1.ListImagesRequest( + project=project, max_results=100, filter="deprecated.state != DEPRECATED" + ) # 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}") + + # [END compute_images_list] @@ -60,21 +64,26 @@ def print_images_list_by_page(project: str, page_size: int = 10) -> None: """ 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") + images_list_request = compute_v1.ListImagesRequest( + project=project, max_results=page_size, filter="deprecated.state != DEPRECATED" + ) # 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): + for page_num, page in enumerate( + images_client.list(request=images_list_request).pages, start=1 + ): print(f"Page {page_num}: ") for img in page.items: print(f" - {img.name}") + + # [END compute_images_list_page] -if __name__ == '__main__': +if __name__ == "__main__": print("=================== Flat list of images ===================") - print_images_list('windows-sql-cloud') + print_images_list("windows-sql-cloud") print("================= Paginated list of images ================") - print_images_list_by_page('windows-sql-cloud', 5) + print_images_list_by_page("windows-sql-cloud", 5) diff --git a/compute/compute/snippets/sample_start_stop.py b/compute/compute/snippets/sample_start_stop.py index 6f3c87e42cc..cce524fad87 100644 --- a/compute/compute/snippets/sample_start_stop.py +++ b/compute/compute/snippets/sample_start_stop.py @@ -46,15 +46,17 @@ def start_instance(project_id: str, zone: str, instance_name: str): op = instance_client.start(project=project_id, zone=zone, instance=instance_name) while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait( - operation=op.name, zone=zone, project=project_id - ) + op = op_client.wait(operation=op.name, zone=zone, project=project_id) return + + # [END compute_start_instance] # [START compute_start_enc_instance] -def start_instance_with_encryption_key(project_id: str, zone: str, instance_name: str, key: bytes): +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). @@ -69,7 +71,9 @@ def start_instance_with_encryption_key(project_id: str, zone: str, instance_name instance_client = compute_v1.InstancesClient() op_client = compute_v1.ZoneOperationsClient() - instance_data = instance_client.get(project=project_id, zone=zone, instance=instance_name) + instance_data = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) # Prepare the information about disk encryption disk_data = compute_v1.CustomerEncryptionKeyProtectedDisk() @@ -81,14 +85,18 @@ def start_instance_with_encryption_key(project_id: str, zone: str, instance_name enc_data = compute_v1.InstancesStartWithEncryptionKeyRequest() enc_data.disks = [disk_data] - op = instance_client.start_with_encryption_key(project=project_id, zone=zone, instance=instance_name, - instances_start_with_encryption_key_request_resource=enc_data) + op = instance_client.start_with_encryption_key( + project=project_id, + zone=zone, + instance=instance_name, + instances_start_with_encryption_key_request_resource=enc_data, + ) while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait( - operation=op.name, zone=zone, project=project_id - ) + op = op_client.wait(operation=op.name, zone=zone, project=project_id) return + + # [END compute_start_enc_instance] @@ -108,10 +116,10 @@ def stop_instance(project_id: str, zone: str, instance_name: str): op = instance_client.stop(project=project_id, zone=zone, instance=instance_name) while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait( - operation=op.name, zone=zone, project=project_id - ) + op = op_client.wait(operation=op.name, zone=zone, project=project_id) return + + # [END compute_stop_instance] @@ -131,8 +139,8 @@ def reset_instance(project_id: str, zone: str, instance_name: str): op = instance_client.reset(project=project_id, zone=zone, instance=instance_name) while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait( - operation=op.name, zone=zone, project=project_id - ) + op = op_client.wait(operation=op.name, zone=zone, project=project_id) return + + # [END compute_reset_instance] diff --git a/compute/compute/snippets/sample_templates.py b/compute/compute/snippets/sample_templates.py index 99bf5bdd5b6..3ec1600bf13 100644 --- a/compute/compute/snippets/sample_templates.py +++ b/compute/compute/snippets/sample_templates.py @@ -14,6 +14,7 @@ # [START compute_template_list ] from typing import Iterable + # [END compute_template_list ] # [START compute_template_create ] @@ -23,6 +24,7 @@ # [START compute_template_create_with_subnet ] # [START compute_template_delete ] from google.cloud import compute_v1 + # [END compute_template_delete ] # [END compute_template_create_with_subnet ] # [END compute_template_create_from_instance ] @@ -32,7 +34,9 @@ # [START compute_template_get ] -def get_instance_template(project_id: str, template_name: str) -> compute_v1.InstanceTemplate: +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). @@ -46,6 +50,8 @@ def get_instance_template(project_id: str, template_name: str) -> compute_v1.Ins """ template_client = compute_v1.InstanceTemplatesClient() return template_client.get(project=project_id, instance_template=template_name) + + # [END compute_template_get ] @@ -62,6 +68,8 @@ def list_instance_templates(project_id: str) -> Iterable[compute_v1.InstanceTemp """ template_client = compute_v1.InstanceTemplatesClient() return template_client.list(project=project_id) + + # [END compute_template_list ] @@ -98,10 +106,8 @@ def create_template(project_id: str, template_name: str) -> compute_v1.InstanceT # The template lets the instance use an external IP address. access_config = compute_v1.AccessConfig() access_config.name = "External NAT" - access_config.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT - access_config.network_tier = ( - compute_v1.AccessConfig.NetworkTier.PREMIUM - ) + access_config.type_ = "ONE_TO_ONE_NAT" + access_config.network_tier = "PREMIUM" network_interface.access_configs = [access_config] template = compute_v1.InstanceTemplate() @@ -116,11 +122,15 @@ def create_template(project_id: str, template_name: str) -> compute_v1.InstanceT operation_client.wait(project=project_id, operation=op.name) return template_client.get(project=project_id, instance_template=template_name) + + # [END compute_template_create ] # [START compute_template_create_from_instance ] -def create_template_from_instance(project_id: str, instance: str, template_name: str) -> compute_v1.InstanceTemplate: +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. @@ -139,9 +149,7 @@ def create_template_from_instance(project_id: str, instance: str, template_name: # 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 = ( - compute_v1.DiskInstantiationConfig.InstantiateFrom.CUSTOM_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 @@ -158,6 +166,8 @@ def create_template_from_instance(project_id: str, instance: str, template_name: operation_client.wait(project=project_id, operation=op.name) return template_client.get(project=project_id, instance_template=template_name) + + # [END compute_template_create_from_instance ] @@ -209,6 +219,8 @@ def create_template_with_subnet( operation_client.wait(project=project_id, operation=op.name) return template_client.get(project=project_id, instance_template=template_name) + + # [END compute_template_create_with_subnet ] @@ -226,4 +238,6 @@ def delete_instance_template(project_id: str, template_name: str): op = template_client.delete(project=project_id, instance_template=template_name) operation_client.wait(project=project_id, operation=op.name) return + + # [END compute_template_delete ] diff --git a/compute/compute/snippets/test_sample_create_vm.py b/compute/compute/snippets/test_sample_create_vm.py index d610d4776fe..2b6d69fa3f0 100644 --- a/compute/compute/snippets/test_sample_create_vm.py +++ b/compute/compute/snippets/test_sample_create_vm.py @@ -29,7 +29,7 @@ ) PROJECT = google.auth.default()[1] -REGION = 'us-central1' +REGION = "us-central1" INSTANCE_ZONE = "us-central1-b" @@ -117,11 +117,7 @@ def test_create_from_custom_image(self): def test_create_from_public_image(self): instance_name = "i" + uuid.uuid4().hex[:10] - instance = create_from_public_image( - PROJECT, - INSTANCE_ZONE, - instance_name, - ) + instance = create_from_public_image(PROJECT, INSTANCE_ZONE, instance_name,) try: assert "debian-cloud" in instance.disks[0].initialize_params.source_image assert "debian-10" in instance.disks[0].initialize_params.source_image @@ -182,6 +178,9 @@ def test_create_with_subnet(self): ) try: assert instance.network_interfaces[0].name == "global/networks/default" - assert instance.network_interfaces[0].subnetwork == f"regions/{REGION}/subnetworks/default" + assert ( + instance.network_interfaces[0].subnetwork + == f"regions/{REGION}/subnetworks/default" + ) finally: delete_instance(PROJECT, INSTANCE_ZONE, instance_name) diff --git a/compute/compute/snippets/test_sample_default_values.py b/compute/compute/snippets/test_sample_default_values.py index 16216101c24..231e00675db 100644 --- a/compute/compute/snippets/test_sample_default_values.py +++ b/compute/compute/snippets/test_sample_default_values.py @@ -19,12 +19,15 @@ import google.cloud.storage as storage import pytest -from sample_default_values import \ - disable_usage_export, get_usage_export_bucket, set_usage_export_bucket +from sample_default_values import ( + disable_usage_export, + get_usage_export_bucket, + set_usage_export_bucket, +) PROJECT = google.auth.default()[1] BUCKET_NAME = "test" + uuid.uuid4().hex[:10] -TEST_PREFIX = 'some-prefix' +TEST_PREFIX = "some-prefix" @pytest.fixture @@ -35,35 +38,37 @@ def temp_bucket(): bucket.delete(force=True) -def test_set_usage_export_bucket_default(capsys: typing.Any, - temp_bucket: storage.Bucket) -> None: +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') + 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) + 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 == '') + 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) + 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) + assert uel.bucket_name == temp_bucket.name + assert uel.report_name_prefix == TEST_PREFIX out, _ = capsys.readouterr() - assert('usage_gce' not in out) + 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 == '') + assert uel.bucket_name == "" + assert uel.report_name_prefix == "" diff --git a/compute/compute/snippets/test_sample_firewall.py b/compute/compute/snippets/test_sample_firewall.py index 9f99bfdbfbb..8148febbbcf 100644 --- a/compute/compute/snippets/test_sample_firewall.py +++ b/compute/compute/snippets/test_sample_firewall.py @@ -34,7 +34,7 @@ def firewall_rule(): firewall_rule = compute_v1.Firewall() firewall_rule.name = "firewall-sample-test" + uuid.uuid4().hex[:10] - firewall_rule.direction = compute_v1.Firewall.Direction.INGRESS + firewall_rule.direction = "INGRESS" allowed_ports = compute_v1.Allowed() allowed_ports.I_p_protocol = "tcp" allowed_ports.ports = ["80"] @@ -42,7 +42,7 @@ def firewall_rule(): 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_rule.target_tags = ["web"] firewall_client = compute_v1.FirewallsClient() op = firewall_client.insert(project=PROJECT, firewall_resource=firewall_rule) @@ -61,7 +61,7 @@ def test_create_delete(): create_firewall_rule(PROJECT, rule_name) rule = get_firewall_rule(PROJECT, rule_name) assert rule.name == rule_name - assert 'web' in rule.target_tags + assert "web" in rule.target_tags delete_firewall_rule(PROJECT, rule_name) assert all(rule.name != rule_name for rule in list_firewall_rules(PROJECT)) diff --git a/compute/compute/snippets/test_sample_pagination.py b/compute/compute/snippets/test_sample_pagination.py index c881ccd1ae9..77672ba50c3 100644 --- a/compute/compute/snippets/test_sample_pagination.py +++ b/compute/compute/snippets/test_sample_pagination.py @@ -15,16 +15,16 @@ from sample_pagination import print_images_list, print_images_list_by_page -PROJECT = 'windows-sql-cloud' +PROJECT = "windows-sql-cloud" def test_pagination(capsys: typing.Any) -> None: print_images_list(PROJECT) out, _ = capsys.readouterr() - assert(len(out.splitlines()) > 2) + assert len(out.splitlines()) > 2 def test_pagination_page(capsys: typing.Any) -> None: print_images_list_by_page(PROJECT, 2) out, _ = capsys.readouterr() - assert("Page 2" in out) + assert "Page 2" in out diff --git a/compute/compute/snippets/test_sample_start_stop.py b/compute/compute/snippets/test_sample_start_stop.py index 2d6aac5c278..734589be109 100644 --- a/compute/compute/snippets/test_sample_start_stop.py +++ b/compute/compute/snippets/test_sample_start_stop.py @@ -22,14 +22,20 @@ import pytest -from sample_start_stop import start_instance, start_instance_with_encryption_key, stop_instance +from sample_start_stop import ( + start_instance, + start_instance_with_encryption_key, + 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=' +KEY_B64 = base64.b64encode( + KEY.encode() +) # for example: b'VEdORldtY3NKellPdWRDcUF5YlNVREtJdm5qaFJYSFA=' def _make_disk(raw_key: bytes = None): @@ -42,8 +48,8 @@ def _make_disk(raw_key: bytes = None): disk.initialize_params = initialize_params disk.auto_delete = True disk.boot = True - disk.type_ = compute_v1.AttachedDisk.Type.PERSISTENT - disk.device_name = 'disk-1' + disk.type_ = "PERSISTENT" + disk.device_name = "disk-1" if raw_key: disk.disk_encryption_key = compute_v1.CustomerEncryptionKey() @@ -54,7 +60,7 @@ def _make_disk(raw_key: bytes = None): def _make_request(disk: compute_v1.AttachedDisk): network_interface = compute_v1.NetworkInterface() - network_interface.name = 'default' + network_interface.name = "default" network_interface.access_configs = [] # Collect information into the Instance object. @@ -83,14 +89,18 @@ def _create_instance(request: compute_v1.InsertInstanceRequest): operation=operation.name, zone=INSTANCE_ZONE, project=PROJECT ) - return instance_client.get(project=PROJECT, zone=INSTANCE_ZONE, instance=request.instance_resource.name) + 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(project=PROJECT, zone=INSTANCE_ZONE, instance=instance.name) + operation = instance_client.delete( + 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 @@ -99,7 +109,9 @@ def _delete_instance(instance: compute_v1.Instance): 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 + return instance_client.get( + project=PROJECT, zone=INSTANCE_ZONE, instance=instance.name + ).status @pytest.fixture @@ -127,31 +139,33 @@ def compute_encrypted_instance(): def test_instance_operations(compute_instance): - assert _get_status(compute_instance) == compute_v1.Instance.Status.RUNNING + assert _get_status(compute_instance) == "RUNNING" stop_instance(PROJECT, INSTANCE_ZONE, compute_instance.name) - while _get_status(compute_instance) == compute_v1.Instance.Status.STOPPING: + 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) == compute_v1.Instance.Status.TERMINATED + assert _get_status(compute_instance) == "TERMINATED" start_instance(PROJECT, INSTANCE_ZONE, compute_instance.name) - assert _get_status(compute_instance) == compute_v1.Instance.Status.RUNNING + assert _get_status(compute_instance) == "RUNNING" def test_instance_encrypted(compute_encrypted_instance): - assert _get_status(compute_encrypted_instance) == compute_v1.Instance.Status.RUNNING + assert _get_status(compute_encrypted_instance) == "RUNNING" stop_instance(PROJECT, INSTANCE_ZONE, compute_encrypted_instance.name) - while _get_status(compute_encrypted_instance) == compute_v1.Instance.Status.STOPPING: + 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) == compute_v1.Instance.Status.TERMINATED + 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) == compute_v1.Instance.Status.RUNNING + start_instance_with_encryption_key( + PROJECT, INSTANCE_ZONE, compute_encrypted_instance.name, KEY_B64 + ) + assert _get_status(compute_encrypted_instance) == "RUNNING" From 2ce228703b0580a9b009a41cc642eb47eb994e34 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 1 Dec 2021 17:11:29 +0100 Subject: [PATCH 042/113] test: Making firewall test tolerant of GCE Enforcer. (#162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(samples): Making firewall test tolerant of GCE Enforcer. * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * docs(samples): Marking the default values test as flaky. Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/requirements-test.txt | 1 + .../compute/snippets/test_sample_default_values.py | 2 ++ compute/compute/snippets/test_sample_firewall.py | 12 ++++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index 19762f901f5..6c0e2549b44 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,2 +1,3 @@ pytest==6.2.5 +flaky==3.7.0 google-cloud-storage==1.43.0 \ No newline at end of file diff --git a/compute/compute/snippets/test_sample_default_values.py b/compute/compute/snippets/test_sample_default_values.py index 231e00675db..23182e0777b 100644 --- a/compute/compute/snippets/test_sample_default_values.py +++ b/compute/compute/snippets/test_sample_default_values.py @@ -15,6 +15,7 @@ import typing import uuid +from flaky import flaky import google.auth import google.cloud.storage as storage import pytest @@ -38,6 +39,7 @@ def temp_bucket(): bucket.delete(force=True) +@flaky(max_runs=3) def test_set_usage_export_bucket_default( capsys: typing.Any, temp_bucket: storage.Bucket ) -> None: diff --git a/compute/compute/snippets/test_sample_firewall.py b/compute/compute/snippets/test_sample_firewall.py index 8148febbbcf..d7bd68b952a 100644 --- a/compute/compute/snippets/test_sample_firewall.py +++ b/compute/compute/snippets/test_sample_firewall.py @@ -14,6 +14,7 @@ import time import uuid +import google.api_core.exceptions import google.auth from google.cloud import compute_v1 import pytest @@ -52,8 +53,15 @@ def firewall_rule(): yield firewall_client.get(project=PROJECT, firewall=firewall_rule.name) - op = firewall_client.delete(project=PROJECT, firewall=firewall_rule.name) - op_client.wait(project=PROJECT, operation=op.name) + try: + op = firewall_client.delete(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 def test_create_delete(): From aa6bc6d477c7be059c39294ba56c33e9e77c507f Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Mon, 6 Dec 2021 18:04:37 +0100 Subject: [PATCH 043/113] docs(samples): Renaming region to avoid name collision with different region. (#167) --- compute/compute/snippets/sample_images.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compute/compute/snippets/sample_images.py b/compute/compute/snippets/sample_images.py index 9ea1ab45984..96d8bbb8302 100644 --- a/compute/compute/snippets/sample_images.py +++ b/compute/compute/snippets/sample_images.py @@ -15,14 +15,14 @@ from typing import Iterable # [START compute_images_get] -# [START compute_images_list] +# [START compute_images_get_list] from google.cloud import compute_v1 -# [END compute_images_list] +# [END compute_images_get_list] # [END compute_images_get] -# [START compute_images_list] +# [START compute_images_get_list] def list_images(project_id: str) -> Iterable[compute_v1.Image]: """ Retrieve a list of images available in given project. @@ -37,7 +37,7 @@ def list_images(project_id: str) -> Iterable[compute_v1.Image]: return image_client.list(project=project_id) -# [END compute_images_list] +# [END compute_images_get_list] # [START compute_images_get] From b1e71c7fade4fe7b89aba9db37eb0dd911400028 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 13 Dec 2021 18:50:15 -0500 Subject: [PATCH 044/113] chore: update to the latest version of the generator (#170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update to the latest version of the generator * update integration tests to reflect changes in the code * update integration tests to reflect changes in the code * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * update samples to reflect changes in the code * update samples to reflect changes in the code * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot --- compute/compute/snippets/quickstart.py | 4 ++-- compute/compute/snippets/sample_create_vm.py | 2 +- .../compute/snippets/sample_default_values.py | 4 ++-- compute/compute/snippets/sample_firewall.py | 10 +++++++--- .../snippets/sample_instance_from_template.py | 4 ++-- compute/compute/snippets/sample_start_stop.py | 10 +++++++--- compute/compute/snippets/sample_templates.py | 16 ++++++++++++---- .../compute/snippets/test_sample_create_vm.py | 16 ++++++++++------ compute/compute/snippets/test_sample_firewall.py | 4 ++-- .../test_sample_instance_from_template.py | 6 ++++-- .../compute/snippets/test_sample_start_stop.py | 4 ++-- 11 files changed, 51 insertions(+), 29 deletions(-) diff --git a/compute/compute/snippets/quickstart.py b/compute/compute/snippets/quickstart.py index fa481f8c9be..bdbdf4fc135 100644 --- a/compute/compute/snippets/quickstart.py +++ b/compute/compute/snippets/quickstart.py @@ -161,7 +161,7 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert(request=request) + operation = instance_client.insert_unary(request=request) while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id @@ -191,7 +191,7 @@ def delete_instance(project_id: str, zone: str, machine_name: str) -> None: operation_client = compute_v1.ZoneOperationsClient() print(f"Deleting {machine_name} from {zone}...") - operation = instance_client.delete( + operation = instance_client.delete_unary( project=project_id, zone=zone, instance=machine_name ) while operation.status != compute_v1.Operation.Status.DONE: diff --git a/compute/compute/snippets/sample_create_vm.py b/compute/compute/snippets/sample_create_vm.py index 21021e010df..e1ba3907a51 100644 --- a/compute/compute/snippets/sample_create_vm.py +++ b/compute/compute/snippets/sample_create_vm.py @@ -214,7 +214,7 @@ def create_with_disks( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert(request=request) + operation = instance_client.insert_unary(request=request) while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id diff --git a/compute/compute/snippets/sample_default_values.py b/compute/compute/snippets/sample_default_values.py index 705a81d0b7b..35148795279 100644 --- a/compute/compute/snippets/sample_default_values.py +++ b/compute/compute/snippets/sample_default_values.py @@ -58,7 +58,7 @@ def set_usage_export_bucket( ) projects_client = compute_v1.ProjectsClient() - operation = projects_client.set_usage_export_bucket( + operation = projects_client.set_usage_export_bucket_unary( project=project_id, usage_export_location_resource=usage_export_location ) @@ -121,7 +121,7 @@ def disable_usage_export(project_id: str) -> None: # Setting `usage_export_location_resource` to an # empty object will disable the usage report generation. - operation = projects_client.set_usage_export_bucket( + operation = projects_client.set_usage_export_bucket_unary( project=project_id, usage_export_location_resource={} ) diff --git a/compute/compute/snippets/sample_firewall.py b/compute/compute/snippets/sample_firewall.py index b9a0d3d2f99..c2bdd3fa75a 100644 --- a/compute/compute/snippets/sample_firewall.py +++ b/compute/compute/snippets/sample_firewall.py @@ -95,7 +95,9 @@ def create_firewall_rule( # firewall_rule.priority = 0 firewall_client = compute_v1.FirewallsClient() - op = firewall_client.insert(project=project_id, firewall_resource=firewall_rule) + op = firewall_client.insert_unary( + project=project_id, firewall_resource=firewall_rule + ) op_client = compute_v1.GlobalOperationsClient() op_client.wait(project=project_id, operation=op.name) @@ -122,7 +124,7 @@ def patch_firewall_priority(project_id: str, firewall_rule_name: str, 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( + operation = firewall_client.patch_unary( project=project_id, firewall=firewall_rule_name, firewall_resource=firewall_rule ) @@ -144,7 +146,9 @@ def delete_firewall_rule(project_id: str, firewall_rule_name: str): 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) + operation = firewall_client.delete_unary( + project=project_id, firewall=firewall_rule_name + ) operation_client = compute_v1.GlobalOperationsClient() operation_client.wait(project=project_id, operation=operation.name) diff --git a/compute/compute/snippets/sample_instance_from_template.py b/compute/compute/snippets/sample_instance_from_template.py index 5c3e03935c6..30ef65dbae3 100644 --- a/compute/compute/snippets/sample_instance_from_template.py +++ b/compute/compute/snippets/sample_instance_from_template.py @@ -48,7 +48,7 @@ def create_instance_from_template( instance_insert_request.source_instance_template = instance_template_url instance_insert_request.instance_resource.name = instance_name - op = instance_client.insert(instance_insert_request) + op = instance_client.insert_unary(instance_insert_request) operation_client.wait(project=project_id, zone=zone, operation=op.name) return instance_client.get(project=project_id, zone=zone, instance=instance_name) @@ -127,7 +127,7 @@ def create_instance_from_template_with_overrides( instance_insert_request.instance_resource = instance instance_insert_request.source_instance_template = instance_template.self_link - op = instance_client.insert(instance_insert_request) + op = instance_client.insert_unary(instance_insert_request) operation_client.wait(project=project_id, zone=zone, operation=op.name) return instance_client.get(project=project_id, zone=zone, instance=instance_name) diff --git a/compute/compute/snippets/sample_start_stop.py b/compute/compute/snippets/sample_start_stop.py index cce524fad87..1637ee77848 100644 --- a/compute/compute/snippets/sample_start_stop.py +++ b/compute/compute/snippets/sample_start_stop.py @@ -85,7 +85,7 @@ def start_instance_with_encryption_key( enc_data = compute_v1.InstancesStartWithEncryptionKeyRequest() enc_data.disks = [disk_data] - op = instance_client.start_with_encryption_key( + op = instance_client.start_with_encryption_key_unary( project=project_id, zone=zone, instance=instance_name, @@ -113,7 +113,9 @@ def stop_instance(project_id: str, zone: str, instance_name: str): instance_client = compute_v1.InstancesClient() op_client = compute_v1.ZoneOperationsClient() - op = instance_client.stop(project=project_id, zone=zone, instance=instance_name) + op = instance_client.stop_unary( + project=project_id, zone=zone, instance=instance_name + ) while op.status != compute_v1.Operation.Status.DONE: op = op_client.wait(operation=op.name, zone=zone, project=project_id) @@ -136,7 +138,9 @@ def reset_instance(project_id: str, zone: str, instance_name: str): instance_client = compute_v1.InstancesClient() op_client = compute_v1.ZoneOperationsClient() - op = instance_client.reset(project=project_id, zone=zone, instance=instance_name) + op = instance_client.reset_unary( + project=project_id, zone=zone, instance=instance_name + ) while op.status != compute_v1.Operation.Status.DONE: op = op_client.wait(operation=op.name, zone=zone, project=project_id) diff --git a/compute/compute/snippets/sample_templates.py b/compute/compute/snippets/sample_templates.py index 3ec1600bf13..ddea063770a 100644 --- a/compute/compute/snippets/sample_templates.py +++ b/compute/compute/snippets/sample_templates.py @@ -118,7 +118,9 @@ def create_template(project_id: str, template_name: str) -> compute_v1.InstanceT template_client = compute_v1.InstanceTemplatesClient() operation_client = compute_v1.GlobalOperationsClient() - op = template_client.insert(project=project_id, instance_template_resource=template) + op = template_client.insert_unary( + project=project_id, instance_template_resource=template + ) operation_client.wait(project=project_id, operation=op.name) return template_client.get(project=project_id, instance_template=template_name) @@ -162,7 +164,9 @@ def create_template_from_instance( template_client = compute_v1.InstanceTemplatesClient() operation_client = compute_v1.GlobalOperationsClient() - op = template_client.insert(project=project_id, instance_template_resource=template) + op = template_client.insert_unary( + project=project_id, instance_template_resource=template + ) operation_client.wait(project=project_id, operation=op.name) return template_client.get(project=project_id, instance_template=template_name) @@ -215,7 +219,9 @@ def create_template_with_subnet( template_client = compute_v1.InstanceTemplatesClient() operation_client = compute_v1.GlobalOperationsClient() - op = template_client.insert(project=project_id, instance_template_resource=template) + op = template_client.insert_unary( + project=project_id, instance_template_resource=template + ) operation_client.wait(project=project_id, operation=op.name) return template_client.get(project=project_id, instance_template=template_name) @@ -235,7 +241,9 @@ def delete_instance_template(project_id: str, template_name: str): """ template_client = compute_v1.InstanceTemplatesClient() operation_client = compute_v1.GlobalOperationsClient() - op = template_client.delete(project=project_id, instance_template=template_name) + op = template_client.delete_unary( + project=project_id, instance_template=template_name + ) operation_client.wait(project=project_id, operation=op.name) return diff --git a/compute/compute/snippets/test_sample_create_vm.py b/compute/compute/snippets/test_sample_create_vm.py index 2b6d69fa3f0..3e3189d904a 100644 --- a/compute/compute/snippets/test_sample_create_vm.py +++ b/compute/compute/snippets/test_sample_create_vm.py @@ -46,7 +46,9 @@ def src_disk(request): disk = compute_v1.Disk() disk.source_image = get_active_debian().self_link disk.name = "test-disk-" + uuid.uuid4().hex[:10] - op = disk_client.insert(project=PROJECT, zone=INSTANCE_ZONE, disk_resource=disk) + op = disk_client.insert_unary( + project=PROJECT, zone=INSTANCE_ZONE, disk_resource=disk + ) wait_for_operation(op, PROJECT) try: @@ -54,7 +56,9 @@ def src_disk(request): request.cls.disk = disk yield disk finally: - op = disk_client.delete(project=PROJECT, zone=INSTANCE_ZONE, disk=disk.name) + op = disk_client.delete_unary( + project=PROJECT, zone=INSTANCE_ZONE, disk=disk.name + ) wait_for_operation(op, PROJECT) @@ -64,7 +68,7 @@ def snapshot(request, src_disk): snapshot = compute_v1.Snapshot() snapshot.name = "test-snap-" + uuid.uuid4().hex[:10] disk_client = compute_v1.DisksClient() - op = disk_client.create_snapshot( + op = disk_client.create_snapshot_unary( project=PROJECT, zone=INSTANCE_ZONE, disk=src_disk.name, @@ -79,7 +83,7 @@ def snapshot(request, src_disk): yield snapshot finally: - op = snapshot_client.delete(project=PROJECT, snapshot=snapshot.name) + op = snapshot_client.delete_unary(project=PROJECT, snapshot=snapshot.name) wait_for_operation(op, PROJECT) @@ -89,7 +93,7 @@ def image(request, src_disk): image = compute_v1.Image() image.source_disk = src_disk.self_link image.name = "test-image-" + uuid.uuid4().hex[:10] - op = image_client.insert(project=PROJECT, image_resource=image) + op = image_client.insert_unary(project=PROJECT, image_resource=image) wait_for_operation(op, PROJECT) try: @@ -97,7 +101,7 @@ def image(request, src_disk): request.cls.image = image yield image finally: - op = image_client.delete(project=PROJECT, image=image.name) + op = image_client.delete_unary(project=PROJECT, image=image.name) wait_for_operation(op, PROJECT) diff --git a/compute/compute/snippets/test_sample_firewall.py b/compute/compute/snippets/test_sample_firewall.py index d7bd68b952a..1bb63cfd8f0 100644 --- a/compute/compute/snippets/test_sample_firewall.py +++ b/compute/compute/snippets/test_sample_firewall.py @@ -46,7 +46,7 @@ def firewall_rule(): firewall_rule.target_tags = ["web"] firewall_client = compute_v1.FirewallsClient() - op = firewall_client.insert(project=PROJECT, firewall_resource=firewall_rule) + op = firewall_client.insert_unary(project=PROJECT, firewall_resource=firewall_rule) op_client = compute_v1.GlobalOperationsClient() op_client.wait(project=PROJECT, operation=op.name) @@ -54,7 +54,7 @@ def firewall_rule(): yield firewall_client.get(project=PROJECT, firewall=firewall_rule.name) try: - op = firewall_client.delete(project=PROJECT, firewall=firewall_rule.name) + 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: diff --git a/compute/compute/snippets/test_sample_instance_from_template.py b/compute/compute/snippets/test_sample_instance_from_template.py index 5728c2c476a..6b17220fd3a 100644 --- a/compute/compute/snippets/test_sample_instance_from_template.py +++ b/compute/compute/snippets/test_sample_instance_from_template.py @@ -51,14 +51,16 @@ def instance_template(): template_client = compute_v1.InstanceTemplatesClient() operation_client = compute_v1.GlobalOperationsClient() - op = template_client.insert(project=PROJECT, instance_template_resource=template) + 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(project=PROJECT, instance_template=template.name) + op = template_client.delete_unary(project=PROJECT, instance_template=template.name) operation_client.wait(project=PROJECT, operation=op.name) diff --git a/compute/compute/snippets/test_sample_start_stop.py b/compute/compute/snippets/test_sample_start_stop.py index 734589be109..bcf249f22dd 100644 --- a/compute/compute/snippets/test_sample_start_stop.py +++ b/compute/compute/snippets/test_sample_start_stop.py @@ -83,7 +83,7 @@ def _create_instance(request: compute_v1.InsertInstanceRequest): instance_client = compute_v1.InstancesClient() operation_client = compute_v1.ZoneOperationsClient() - operation = instance_client.insert(request=request) + 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 @@ -98,7 +98,7 @@ def _delete_instance(instance: compute_v1.Instance): instance_client = compute_v1.InstancesClient() operation_client = compute_v1.ZoneOperationsClient() - operation = instance_client.delete( + operation = instance_client.delete_unary( project=PROJECT, zone=INSTANCE_ZONE, instance=instance.name ) while operation.status != compute_v1.Operation.Status.DONE: From 8e443aa78bccf32b666a96bef6c1e32a3dffed43 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 13 Dec 2021 19:20:48 -0500 Subject: [PATCH 045/113] chore: release 0.9.0 (#161) * chore: release 0.9.0 * update samples to reflect changes in the code Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/sample_start_stop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/sample_start_stop.py b/compute/compute/snippets/sample_start_stop.py index 1637ee77848..80eb95f8e4e 100644 --- a/compute/compute/snippets/sample_start_stop.py +++ b/compute/compute/snippets/sample_start_stop.py @@ -43,7 +43,7 @@ def start_instance(project_id: str, zone: str, instance_name: str): instance_client = compute_v1.InstancesClient() op_client = compute_v1.ZoneOperationsClient() - op = instance_client.start(project=project_id, zone=zone, instance=instance_name) + op = instance_client.start_unary(project=project_id, zone=zone, instance=instance_name) while op.status != compute_v1.Operation.Status.DONE: op = op_client.wait(operation=op.name, zone=zone, project=project_id) From 3a81c03bfe46f6a8a5bdb468724b93e7655b976d Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 14 Dec 2021 11:40:01 +0100 Subject: [PATCH 046/113] chore(deps): update dependency google-cloud-compute to v0.9.0 (#171) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update dependency google-cloud-compute to v0.9.0 * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot --- compute/compute/snippets/requirements.txt | 2 +- compute/compute/snippets/sample_start_stop.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/compute/compute/snippets/requirements.txt b/compute/compute/snippets/requirements.txt index 3d8cb4e6cb2..e2693e6f275 100644 --- a/compute/compute/snippets/requirements.txt +++ b/compute/compute/snippets/requirements.txt @@ -1 +1 @@ -google-cloud-compute==0.8.0 \ No newline at end of file +google-cloud-compute==0.9.0 \ No newline at end of file diff --git a/compute/compute/snippets/sample_start_stop.py b/compute/compute/snippets/sample_start_stop.py index 80eb95f8e4e..a73f05ba53f 100644 --- a/compute/compute/snippets/sample_start_stop.py +++ b/compute/compute/snippets/sample_start_stop.py @@ -43,7 +43,9 @@ def start_instance(project_id: str, zone: str, instance_name: str): instance_client = compute_v1.InstancesClient() op_client = compute_v1.ZoneOperationsClient() - op = instance_client.start_unary(project=project_id, zone=zone, instance=instance_name) + op = instance_client.start_unary( + project=project_id, zone=zone, instance=instance_name + ) while op.status != compute_v1.Operation.Status.DONE: op = op_client.wait(operation=op.name, zone=zone, project=project_id) From bf696870eee88aaf752d2a849319c5ab8f02ec62 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Tue, 14 Dec 2021 17:54:52 +0100 Subject: [PATCH 047/113] docs(samples): Samples for custom vm types (#155) * chore(docs): Adding samples for Custom VM Types. Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/quickstart.py | 7 +- compute/compute/snippets/sample_create_vm.py | 8 +- .../compute/snippets/sample_custom_types.py | 531 ++++++++++++++++++ compute/compute/snippets/test_quickstart.py | 2 +- .../snippets/test_sample_custom_types.py | 194 +++++++ 5 files changed, 736 insertions(+), 6 deletions(-) create mode 100644 compute/compute/snippets/sample_custom_types.py create mode 100644 compute/compute/snippets/test_sample_custom_types.py diff --git a/compute/compute/snippets/quickstart.py b/compute/compute/snippets/quickstart.py index bdbdf4fc135..3303cc31703 100644 --- a/compute/compute/snippets/quickstart.py +++ b/compute/compute/snippets/quickstart.py @@ -21,6 +21,7 @@ # [START compute_instances_create] # [START compute_instances_delete] +import re import sys # [START compute_instances_list] @@ -149,8 +150,10 @@ def create_instance( instance = compute_v1.Instance() instance.name = instance_name instance.disks = [disk] - full_machine_type_name = f"zones/{zone}/machineTypes/{machine_type}" - instance.machine_type = full_machine_type_name + 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}" instance.network_interfaces = [network_interface] # Prepare the request to insert an instance. diff --git a/compute/compute/snippets/sample_create_vm.py b/compute/compute/snippets/sample_create_vm.py index e1ba3907a51..5b4377d9804 100644 --- a/compute/compute/snippets/sample_create_vm.py +++ b/compute/compute/snippets/sample_create_vm.py @@ -11,7 +11,7 @@ # WITHOUT 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 re import sys from typing import List @@ -194,8 +194,10 @@ def create_with_disks( instance = compute_v1.Instance() instance.name = instance_name instance.disks = disks - full_machine_type_name = f"zones/{zone}/machineTypes/{machine_type}" - instance.machine_type = full_machine_type_name + 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}" instance.network_interfaces = [network_interface] # Shielded Instance settings diff --git a/compute/compute/snippets/sample_custom_types.py b/compute/compute/snippets/sample_custom_types.py new file mode 100644 index 00000000000..38ad64b8171 --- /dev/null +++ b/compute/compute/snippets/sample_custom_types.py @@ -0,0 +1,531 @@ +# 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. +# [START compute_custom_machine_type_create ] +from collections import namedtuple +from enum import Enum, unique +import sys +import time +from typing import Union + +from google.cloud import compute_v1 + + +# [END compute_custom_machine_type_create ] + + +# [START compute_custom_machine_type_helper_class ] +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", + ], + ) + + 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] + self.core_count = 2 if self.is_shared() else core_count + self.memory_mb = memory_mb + + self._check() + 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: + # Assuming this runs after _check() and the total memory requested is correct + return self.memory_mb > self.core_count * self.limits.max_mem_per_core + + def _check(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." + ) + + 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 ] + + +# [START compute_custom_machine_type_create ] +def create_instance( + project_id: str, + zone: str, + instance_name: str, + machine_type: Union[str, "CustomMachineType"], +): + """ + 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. + 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" + OR + It can be a CustomMachineType object, describing a custom type + you want to use. + + Return: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + operation_client = compute_v1.ZoneOperationsClient() + + # Describe 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-10" + ) + initialize_params.disk_size_gb = 10 + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + disk.type_ = compute_v1.AttachedDisk.Type.PERSISTENT.name + + # Use the network interface provided in the network_name argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = "global/networks/default" + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.name = instance_name + instance.disks = [disk] + instance.machine_type = str(machine_type) + instance.network_interfaces = [network_interface] + + # 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 of type {instance.machine_type} in {zone}..." + ) + operation = instance_client.insert_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance.name) + + +# [END compute_custom_machine_type_create ] + + +# [START compute_custom_machine_type_create_with_helper ] +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) + return create_instance(project_id, zone, instance_name, custom_type) + + +# [END compute_custom_machine_type_create_with_helper ] + + +# [START compute_custom_machine_type_create_shared_with_helper ] +def create_custom_shared_core_instance( + project_id: str, + zone: str, + instance_name: str, + cpu_series: CustomMachineType.CPUSeries, + memory: int, +): + """ + 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) + return create_instance(project_id, zone, instance_name, custom_type) + + +# [END compute_custom_machine_type_create_shared_with_helper ] + + +# [START compute_custom_machine_type_create_without_helper ] +def create_custom_instances_no_helper( + project_id: str, zone: str, instance_name: str, core_count: int, memory: int +): + """ + Create 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. + """ + # 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", + f"zones/{zone}/machineTypes/custom-{core_count}-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2", + f"zones/{zone}/machineTypes/n2-custom-{core_count}-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2d", + f"zones/{zone}/machineTypes/n2d-custom-{core_count}-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_e2", + f"zones/{zone}/machineTypes/e2-custom-{core_count}-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_e2_micro", + f"zones/{zone}/machineTypes/e2-custom-micro-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_e2_small", + f"zones/{zone}/machineTypes/e2-custom-small-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_e2_medium", + f"zones/{zone}/machineTypes/e2-custom-medium-{memory}", + ), + ] + return instances + + +# [END compute_custom_machine_type_create_without_helper ] + + +# [START compute_custom_machine_type_extra_mem_no_helper ] +def create_custom_instances_extra_mem_no_helper( + project_id: str, zone: str, instance_name: str, core_count: int, memory: int +): + """ + Create 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. + """ + # 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", + f"zones/{zone}/machineTypes/custom-{core_count}-{memory}-ext", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2_extra_mem", + f"zones/{zone}/machineTypes/n2-custom-{core_count}-{memory}-ext", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2d_extra_mem", + f"zones/{zone}/machineTypes/n2d-custom-{core_count}-{memory}-ext", + ), + ] + return instances + + +# [END compute_custom_machine_type_extra_mem_no_helper ] + + +# [START compute_custom_machine_type_update_memory ] +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() + operation_client = compute_v1.ZoneOperationsClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + + # Make sure that the machine is turned off + if instance.status not in ( + instance.Status.TERMINATED.name, + instance.Status.STOPPED.name, + ): + op = instance_client.stop_unary( + project=project_id, zone=zone, instance=instance_name + ) + operation_client.wait(project=project_id, zone=zone, operation=op.name) + 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) + + # 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" + # 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) + op = instance_client.update_unary( + project=project_id, + zone=zone, + instance=instance_name, + instance_resource=instance, + ) + operation_client.wait(project=project_id, zone=zone, operation=op.name) + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +# [END compute_custom_machine_type_update_memory ] diff --git a/compute/compute/snippets/test_quickstart.py b/compute/compute/snippets/test_quickstart.py index 91a2d3642ab..7057076568a 100644 --- a/compute/compute/snippets/test_quickstart.py +++ b/compute/compute/snippets/test_quickstart.py @@ -18,7 +18,7 @@ import google.auth -from samples.snippets.quickstart import main +from quickstart import main PROJECT = google.auth.default()[1] INSTANCE_NAME = "i" + uuid.uuid4().hex[:10] diff --git a/compute/compute/snippets/test_sample_custom_types.py b/compute/compute/snippets/test_sample_custom_types.py new file mode 100644 index 00000000000..812b04b5083 --- /dev/null +++ b/compute/compute/snippets/test_sample_custom_types.py @@ -0,0 +1,194 @@ +# 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 quickstart import create_instance, delete_instance +from sample_custom_types import ( + add_extended_memory_to_instance, + create_custom_instance, + create_custom_shared_core_instance, + CustomMachineType, +) + +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] + instance = create_instance( + PROJECT, INSTANCE_ZONE, instance_name, "n2-custom-8-10240" + ) + yield instance + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_custom_instance_creation(auto_delete_instance_name): + 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): + 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 From 5fc56fbb6afa0e72f4407ecf23bf3d1971ecb535 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 5 Jan 2022 13:56:10 +0100 Subject: [PATCH 048/113] chore(samples): Fixing a small issue with sample region tags (#180) --- compute/compute/snippets/sample_create_vm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compute/compute/snippets/sample_create_vm.py b/compute/compute/snippets/sample_create_vm.py index 5b4377d9804..1ea6ba2d2e6 100644 --- a/compute/compute/snippets/sample_create_vm.py +++ b/compute/compute/snippets/sample_create_vm.py @@ -20,11 +20,11 @@ # [START compute_instances_create_from_snapshot] # [START compute_instances_create_from_image_plus_empty_disk] # [START compute_instances_create_from_custom_image] -# [START compute_instances_create_from_image ] +# [START compute_instances_create_from_image] from google.cloud import compute_v1 -# [END compute_instances_create_from_image ] +# [END compute_instances_create_from_image] # [END compute_instances_create_from_custom_image] # [END compute_instances_create_from_image_plus_empty_disk] # [END compute_instances_create_from_snapshot] From 468d91174c9cecf21b6fb8728f0a07f91734faa9 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Mon, 10 Jan 2022 20:09:19 +0100 Subject: [PATCH 049/113] chore(samples): Removing some leaked instances. (#179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(samples): Removing some leaked instances. * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore(samples): Removing more leaked instances. * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore(samples): Removing cleanup code, leaving only the firewall test fix. * chore(samples): Restructuring firewall test. Co-authored-by: Owl Bot --- .../compute/snippets/test_sample_firewall.py | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/compute/compute/snippets/test_sample_firewall.py b/compute/compute/snippets/test_sample_firewall.py index 1bb63cfd8f0..51717439528 100644 --- a/compute/compute/snippets/test_sample_firewall.py +++ b/compute/compute/snippets/test_sample_firewall.py @@ -24,7 +24,6 @@ create_firewall_rule, delete_firewall_rule, get_firewall_rule, - list_firewall_rules, patch_firewall_priority, ) @@ -64,14 +63,29 @@ def firewall_rule(): raise err -def test_create_delete(): +@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] - create_firewall_rule(PROJECT, rule_name) - rule = get_firewall_rule(PROJECT, rule_name) - assert rule.name == rule_name + 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 - delete_firewall_rule(PROJECT, rule_name) - assert all(rule.name != rule_name for rule in list_firewall_rules(PROJECT)) def test_patch_rule(firewall_rule): From 46669e48099477d226a2309e096398256381d96c Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 11 Jan 2022 16:15:50 +0100 Subject: [PATCH 050/113] chore(deps): update dependency google-cloud-storage to v1.44.0 (#182) Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index 6c0e2549b44..3d7f0afb70a 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,3 +1,3 @@ pytest==6.2.5 flaky==3.7.0 -google-cloud-storage==1.43.0 \ No newline at end of file +google-cloud-storage==1.44.0 \ No newline at end of file From a4d52f43130bc28c4c61f5a3a76092d1e7859c2e Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Tue, 11 Jan 2022 11:19:35 -0500 Subject: [PATCH 051/113] chore(samples): Add check for tests in directory (#186) Source-Link: https://github.com/googleapis/synthtool/commit/52aef91f8d25223d9dbdb4aebd94ba8eea2101f3 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:36a95b8f494e4674dc9eee9af98961293b51b86b3649942aac800ae6c1f796d4 Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/noxfile.py | 70 ++++++++++++++++------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/compute/compute/snippets/noxfile.py b/compute/compute/snippets/noxfile.py index 93a9122cc45..3bbef5d54f4 100644 --- a/compute/compute/snippets/noxfile.py +++ b/compute/compute/snippets/noxfile.py @@ -14,6 +14,7 @@ from __future__ import print_function +import glob import os from pathlib import Path import sys @@ -184,37 +185,44 @@ def blacken(session: nox.sessions.Session) -> None: def _session_tests( session: nox.sessions.Session, post_install: Callable = None ) -> None: - if TEST_CONFIG["pip_version_override"]: - pip_version = TEST_CONFIG["pip_version_override"] - session.install(f"pip=={pip_version}") - """Runs py.test for a particular project.""" - if os.path.exists("requirements.txt"): - if os.path.exists("constraints.txt"): - session.install("-r", "requirements.txt", "-c", "constraints.txt") - else: - session.install("-r", "requirements.txt") - - if os.path.exists("requirements-test.txt"): - if os.path.exists("constraints-test.txt"): - session.install("-r", "requirements-test.txt", "-c", "constraints-test.txt") - else: - session.install("-r", "requirements-test.txt") - - if INSTALL_LIBRARY_FROM_SOURCE: - session.install("-e", _get_repo_root()) - - if post_install: - post_install(session) - - session.run( - "pytest", - *(PYTEST_COMMON_ARGS + session.posargs), - # Pytest will return 5 when no tests are collected. This can happen - # on travis where slow and flaky tests are excluded. - # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html - success_codes=[0, 5], - env=get_pytest_env_vars(), - ) + # check for presence of tests + test_list = glob.glob("*_test.py") + glob.glob("test_*.py") + if len(test_list) == 0: + print("No tests found, skipping directory.") + else: + if TEST_CONFIG["pip_version_override"]: + pip_version = TEST_CONFIG["pip_version_override"] + session.install(f"pip=={pip_version}") + """Runs py.test for a particular project.""" + if os.path.exists("requirements.txt"): + if os.path.exists("constraints.txt"): + session.install("-r", "requirements.txt", "-c", "constraints.txt") + else: + session.install("-r", "requirements.txt") + + if os.path.exists("requirements-test.txt"): + if os.path.exists("constraints-test.txt"): + session.install( + "-r", "requirements-test.txt", "-c", "constraints-test.txt" + ) + else: + session.install("-r", "requirements-test.txt") + + if INSTALL_LIBRARY_FROM_SOURCE: + session.install("-e", _get_repo_root()) + + if post_install: + post_install(session) + + session.run( + "pytest", + *(PYTEST_COMMON_ARGS + session.posargs), + # Pytest will return 5 when no tests are collected. This can happen + # on travis where slow and flaky tests are excluded. + # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html + success_codes=[0, 5], + env=get_pytest_env_vars(), + ) @nox.session(python=ALL_VERSIONS) From 072edbe51346afa04a5c6a940b7dd3e8bae94db5 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 14 Jan 2022 22:21:26 +0100 Subject: [PATCH 052/113] chore(deps): update all dependencies (#187) Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/requirements-test.txt | 2 +- compute/compute/snippets/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index 3d7f0afb70a..7034a2313f0 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,3 +1,3 @@ pytest==6.2.5 flaky==3.7.0 -google-cloud-storage==1.44.0 \ No newline at end of file +google-cloud-storage==2.0.0 \ No newline at end of file diff --git a/compute/compute/snippets/requirements.txt b/compute/compute/snippets/requirements.txt index e2693e6f275..16e20f3778a 100644 --- a/compute/compute/snippets/requirements.txt +++ b/compute/compute/snippets/requirements.txt @@ -1 +1 @@ -google-cloud-compute==0.9.0 \ No newline at end of file +google-cloud-compute==1.0.0 \ No newline at end of file From 444c4846f3038f9b0fe1e90bf54dfb574bac960b Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Tue, 18 Jan 2022 20:47:58 -0500 Subject: [PATCH 053/113] chore(python): Noxfile recognizes that tests can live in a folder (#197) Source-Link: https://github.com/googleapis/synthtool/commit/4760d8dce1351d93658cb11d02a1b7ceb23ae5d7 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:f0e4b51deef56bed74d3e2359c583fc104a8d6367da3984fc5c66938db738828 Co-authored-by: Owl Bot --- compute/compute/snippets/noxfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compute/compute/snippets/noxfile.py b/compute/compute/snippets/noxfile.py index 3bbef5d54f4..20cdfc62013 100644 --- a/compute/compute/snippets/noxfile.py +++ b/compute/compute/snippets/noxfile.py @@ -187,6 +187,7 @@ def _session_tests( ) -> None: # check for presence of tests test_list = glob.glob("*_test.py") + glob.glob("test_*.py") + test_list.extend(glob.glob("tests")) if len(test_list) == 0: print("No tests found, skipping directory.") else: From a9a70516ba605d7ce41800075ddf3ee5132b5860 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 19 Jan 2022 15:24:13 +0100 Subject: [PATCH 054/113] chore(samples): Docstring fix (#198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #194 🦕 --- compute/compute/snippets/sample_start_stop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/sample_start_stop.py b/compute/compute/snippets/sample_start_stop.py index a73f05ba53f..66218c74e1b 100644 --- a/compute/compute/snippets/sample_start_stop.py +++ b/compute/compute/snippets/sample_start_stop.py @@ -105,7 +105,7 @@ def start_instance_with_encryption_key( # [START compute_stop_instance] def stop_instance(project_id: str, zone: str, instance_name: str): """ - Stops a stopped Google Compute Engine instance. + Stops a running Google Compute Engine instance. Args: project_id: project ID or project number of the Cloud project your instance belongs to. From 88e856281970fa52550a8fba11e9c34e6aa06e91 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Sat, 22 Jan 2022 15:56:45 +0100 Subject: [PATCH 055/113] chore(deps): update dependency google-cloud-storage to v2.1.0 (#200) * chore(deps): update dependency google-cloud-storage to v2.1.0 * add pin for google-cloud-storage for py3.6 Co-authored-by: Anthonios Partheniou --- compute/compute/snippets/requirements-test.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index 7034a2313f0..54604cd4a3a 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,3 +1,4 @@ pytest==6.2.5 flaky==3.7.0 -google-cloud-storage==2.0.0 \ No newline at end of file +google-cloud-storage==2.0.0; python_version == '3.6' +google-cloud-storage==2.1.0; python_version >= '3.7' From 4f0a8faea24ee65fa9cbf0536e13ec1c177e3b02 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Tue, 8 Feb 2022 18:00:49 +0100 Subject: [PATCH 056/113] docs(samples): Adding samples for delete protection (#208) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(samples): Adding samples for delete protection. * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore(samples): Applying review comment. Co-authored-by: Owl Bot --- .../snippets/sample_delete_protection.py | 147 ++++++++++++++++++ .../snippets/test_sample_delete_protection.py | 52 +++++++ 2 files changed, 199 insertions(+) create mode 100644 compute/compute/snippets/sample_delete_protection.py create mode 100644 compute/compute/snippets/test_sample_delete_protection.py diff --git a/compute/compute/snippets/sample_delete_protection.py b/compute/compute/snippets/sample_delete_protection.py new file mode 100644 index 00000000000..1894d3903bf --- /dev/null +++ b/compute/compute/snippets/sample_delete_protection.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. +# [START compute_delete_protection_create] +import sys + +# [END compute_delete_protection_create] + +# [START compute_delete_protection_get] +# [START compute_delete_protection_set] +# [START compute_delete_protection_create] +from google.cloud import compute_v1 + +# [END compute_delete_protection_create] +# [END compute_delete_protection_set] +# [END compute_delete_protection_get] + + +# [START compute_delete_protection_create] +def create_instance( + project_id: str, zone: str, instance_name: str, delete_protection: bool, +) -> 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 you want to use. For example: “us-west3-b” + instance_name: name of the new virtual machine. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # Describe 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-10" + ) + initialize_params.disk_size_gb = 10 + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + disk.type_ = "PERSISTENT" + + # Use the default VPC network. + network_interface = compute_v1.NetworkInterface() + network_interface.name = "default" + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.name = instance_name + instance.disks = [disk] + instance.machine_type = f"zones/{zone}/machineTypes/e2-small" + instance.network_interfaces = [network_interface] + + # Set the delete protection bit + instance.deletion_protection = delete_protection + + # 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +# [END compute_delete_protection_create] + + +# [START compute_delete_protection_set] +def set_delete_protection( + project_id: str, zone: str, instance_name: str, delete_protection: bool +): + """ + 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 virtual machine to update. + delete_protection: boolean value indicating if the virtual machine should be + protected against deletion or not. + """ + instance_client = compute_v1.InstancesClient() + operation_client = compute_v1.ZoneOperationsClient() + + 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_unary(request) + operation_client.wait(project=project_id, zone=zone, operation=operation.name) + + +# [END compute_delete_protection_set] + + +# [START compute_delete_protection_get] +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 state 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/compute/snippets/test_sample_delete_protection.py b/compute/compute/snippets/test_sample_delete_protection.py new file mode 100644 index 00000000000..ee57a3e22fd --- /dev/null +++ b/compute/compute/snippets/test_sample_delete_protection.py @@ -0,0 +1,52 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 quickstart import delete_instance +from sample_delete_protection import create_instance +from sample_delete_protection import get_delete_protection +from sample_delete_protection 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_instance(PROJECT, INSTANCE_ZONE, autodelete_instance_name, True) + 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 + ) From 554b445b183275a91086367d6d882555be418bec Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Thu, 10 Feb 2022 17:08:15 +0100 Subject: [PATCH 057/113] chore(samples): Fixing test with deprecated OS image. (#217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(samples): Fixing test with deprecated OS image. * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot --- .../compute/snippets/test_sample_instance_from_template.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compute/compute/snippets/test_sample_instance_from_template.py b/compute/compute/snippets/test_sample_instance_from_template.py index 6b17220fd3a..20f1eb7cf5c 100644 --- a/compute/compute/snippets/test_sample_instance_from_template.py +++ b/compute/compute/snippets/test_sample_instance_from_template.py @@ -85,7 +85,9 @@ def test_create_instance_from_template_override( ): image_client = compute_v1.ImagesClient() - image = image_client.get_from_family(project="centos-cloud", family="centos-8") + image = image_client.get_from_family( + project="ubuntu-os-cloud", family="ubuntu-2004-lts" + ) instance = create_instance_from_template_with_overrides( PROJECT, INSTANCE_ZONE, From 719452e95fc6b13afa6402338d8fae72df34c7e0 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Fri, 11 Feb 2022 17:10:24 +0100 Subject: [PATCH 058/113] chore(samples): Samples for preemptible instances (#213) Co-authored-by: Owl Bot Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> --- .../compute/snippets/sample_preemptible.py | 193 ++++++++++++++++++ .../snippets/test_sample_preemptible.py | 58 ++++++ 2 files changed, 251 insertions(+) create mode 100644 compute/compute/snippets/sample_preemptible.py create mode 100644 compute/compute/snippets/test_sample_preemptible.py diff --git a/compute/compute/snippets/sample_preemptible.py b/compute/compute/snippets/sample_preemptible.py new file mode 100644 index 00000000000..3e1b2fc9714 --- /dev/null +++ b/compute/compute/snippets/sample_preemptible.py @@ -0,0 +1,193 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# [START compute_preemptible_history] +import datetime + +# [END compute_preemptible_history] +# [START compute_preemptible_create] +import sys + +# [END compute_preemptible_create] + +# [START compute_preemptible_history] +from typing import List, Tuple + +# [END compute_preemptible_history] + +# [START compute_preemptible_create] +# [START compute_preemptible_check] +# [START compute_preemptible_history] +from google.cloud import compute_v1 + +# [END compute_preemptible_history] +# [END compute_preemptible_check] +# [END compute_preemptible_create] + +# [START compute_preemptible_history] +from google.cloud.compute_v1.services.zone_operations import pagers + +# [END compute_preemptible_history] + + +# [START compute_preemptible_create] +def create_preemptible_instance( + project_id: str, zone: str, instance_name: str, +) -> 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 you want to use. For example: "us-west3-b" + instance_name: name of the new virtual machine. + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + operation_client = compute_v1.ZoneOperationsClient() + + # Describe 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-10" + ) + initialize_params.disk_size_gb = 10 + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + disk.type_ = "PERSISTENT" + + # Use the default VPC network. + network_interface = compute_v1.NetworkInterface() + network_interface.name = "default" + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.name = instance_name + instance.disks = [disk] + instance.machine_type = f"zones/{zone}/machineTypes/e2-small" + instance.network_interfaces = [network_interface] + + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +# [END compute_preemptible_create] + + +# [START compute_preemptible_check] +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] + + +# [START compute_preemptible_history] +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" + instance_name: name of the virtual machine to look for. + 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/compute/snippets/test_sample_preemptible.py b/compute/compute/snippets/test_sample_preemptible.py new file mode 100644 index 00000000000..047a721e370 --- /dev/null +++ b/compute/compute/snippets/test_sample_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 quickstart import delete_instance +from sample_preemptible import create_preemptible_instance +from sample_preemptible import is_preemptible +from sample_preemptible 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." + ) From 5f1d9fde7746687604a1b748feeebf98b2828e93 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 14 Feb 2022 17:55:00 +0100 Subject: [PATCH 059/113] chore(deps): update all dependencies (#215) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [google-cloud-storage](https://togithub.com/googleapis/python-storage) | `==2.0.0` -> `==2.1.0` | [![age](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/2.1.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/2.1.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/2.1.0/compatibility-slim/2.0.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/google-cloud-storage/2.1.0/confidence-slim/2.0.0)](https://docs.renovatebot.com/merge-confidence/) | | [pytest](https://docs.pytest.org/en/latest/) ([source](https://togithub.com/pytest-dev/pytest), [changelog](https://docs.pytest.org/en/stable/changelog.html)) | `==6.2.5` -> `==7.0.1` | [![age](https://badges.renovateapi.com/packages/pypi/pytest/7.0.1/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/pypi/pytest/7.0.1/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/pypi/pytest/7.0.1/compatibility-slim/6.2.5)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/pypi/pytest/7.0.1/confidence-slim/6.2.5)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
googleapis/python-storage ### [`v2.1.0`](https://togithub.com/googleapis/python-storage/blob/HEAD/CHANGELOG.md#​210-httpsgithubcomgoogleapispython-storagecomparev200v210-2022-01-19) [Compare Source](https://togithub.com/googleapis/python-storage/compare/v2.0.0...v2.1.0) ##### Features - add turbo replication support and samples ([#​622](https://togithub.com/googleapis/python-storage/issues/622)) ([4dafc81](https://togithub.com/googleapis/python-storage/commit/4dafc815470480ce9de7f0357e331d3fbd0ae9b7)) - avoid authentication with storage emulator ([#​679](https://togithub.com/googleapis/python-storage/issues/679)) ([8789afa](https://togithub.com/googleapis/python-storage/commit/8789afaaa1b2bd6f03fae72e3d87ce004ec10129)) - remove python 3.6 support ([#​689](https://togithub.com/googleapis/python-storage/issues/689)) ([8aa4130](https://togithub.com/googleapis/python-storage/commit/8aa4130ee068a1922161c8ca54a53a4a51d65ce0))
pytest-dev/pytest ### [`v7.0.1`](https://togithub.com/pytest-dev/pytest/releases/7.0.1) [Compare Source](https://togithub.com/pytest-dev/pytest/compare/7.0.0...7.0.1) # pytest 7.0.1 (2022-02-11) ## Bug Fixes - [#​9608](https://togithub.com/pytest-dev/pytest/issues/9608): Fix invalid importing of `importlib.readers` in Python 3.9. - [#​9610](https://togithub.com/pytest-dev/pytest/issues/9610): Restore \[UnitTestFunction.obj]{.title-ref} to return unbound rather than bound method. Fixes a crash during a failed teardown in unittest TestCases with non-default \[\__init\_\_]{.title-ref}. Regressed in pytest 7.0.0. - [#​9636](https://togithub.com/pytest-dev/pytest/issues/9636): The `pythonpath` plugin was renamed to `python_path`. This avoids a conflict with the `pytest-pythonpath` plugin. - [#​9642](https://togithub.com/pytest-dev/pytest/issues/9642): Fix running tests by id with `::` in the parametrize portion. - [#​9643](https://togithub.com/pytest-dev/pytest/issues/9643): Delay issuing a `~pytest.PytestWarning`{.interpreted-text role="class"} about diamond inheritance involving `~pytest.Item`{.interpreted-text role="class"} and `~pytest.Collector`{.interpreted-text role="class"} so it can be filtered using `standard warning filters `{.interpreted-text role="ref"}. ### [`v7.0.0`](https://togithub.com/pytest-dev/pytest/releases/7.0.0) [Compare Source](https://togithub.com/pytest-dev/pytest/compare/6.2.5...7.0.0) # pytest 7.0.0 (2022-02-03) (**Please see the full set of changes for this release also in the 7.0.0rc1 notes below**) ## Deprecations - [#​9488](https://togithub.com/pytest-dev/pytest/issues/9488): If custom subclasses of nodes like `pytest.Item`{.interpreted-text role="class"} override the `__init__` method, they should take `**kwargs`. See `uncooperative-constructors-deprecated`{.interpreted-text role="ref"} for details. Note that a deprection warning is only emitted when there is a conflict in the arguments pytest expected to pass. This deprecation was already part of pytest 7.0.0rc1 but wasn't documented. ## Bug Fixes - [#​9355](https://togithub.com/pytest-dev/pytest/issues/9355): Fixed error message prints function decorators when using assert in Python 3.8 and above. - [#​9396](https://togithub.com/pytest-dev/pytest/issues/9396): Ensure `pytest.Config.inifile`{.interpreted-text role="attr"} is available during the `pytest_cmdline_main <_pytest.hookspec.pytest_cmdline_main>`{.interpreted-text role="func"} hook (regression during `7.0.0rc1`). ## Improved Documentation - [#​9404](https://togithub.com/pytest-dev/pytest/issues/9404): Added extra documentation on alternatives to common misuses of \[pytest.warns(None)]{.title-ref} ahead of its deprecation. - [#​9505](https://togithub.com/pytest-dev/pytest/issues/9505): Clarify where the configuration files are located. To avoid confusions documentation mentions that configuration file is located in the root of the repository. ## Trivial/Internal Changes - [#​9521](https://togithub.com/pytest-dev/pytest/issues/9521): Add test coverage to assertion rewrite path. # pytest 7.0.0rc1 (2021-12-06) ## Breaking Changes - [#​7259](https://togithub.com/pytest-dev/pytest/issues/7259): The `Node.reportinfo() `{.interpreted-text role="ref"} function first return value type has been expanded from \[py.path.local | str]{.title-ref} to \[os.PathLike\[str] | str]{.title-ref}. Most plugins which refer to \[reportinfo()]{.title-ref} only define it as part of a custom `pytest.Item`{.interpreted-text role="class"} implementation. Since \[py.path.local]{.title-ref} is a \[os.PathLike\[str]]{.title-ref}, these plugins are unaffacted. Plugins and users which call \[reportinfo()]{.title-ref}, use the first return value and interact with it as a \[py.path.local]{.title-ref}, would need to adjust by calling \[py.path.local(fspath)]{.title-ref}. Although preferably, avoid the legacy \[py.path.local]{.title-ref} and use \[pathlib.Path]{.title-ref}, or use \[item.location]{.title-ref} or \[item.path]{.title-ref}, instead. Note: pytest was not able to provide a deprecation period for this change. - [#​8246](https://togithub.com/pytest-dev/pytest/issues/8246): `--version` now writes version information to `stdout` rather than `stderr`. - [#​8733](https://togithub.com/pytest-dev/pytest/issues/8733): Drop a workaround for [pyreadline](https://togithub.com/pyreadline/pyreadline) that made it work with `--pdb`. The workaround was introduced in [#​1281](https://togithub.com/pytest-dev/pytest/pull/1281) in 2015, however since then [pyreadline seems to have gone unmaintained](https://togithub.com/pyreadline/pyreadline/issues/58), is [generating warnings](https://togithub.com/pytest-dev/pytest/issues/8847), and will stop working on Python 3.10. - [#​9061](https://togithub.com/pytest-dev/pytest/issues/9061): Using `pytest.approx`{.interpreted-text role="func"} in a boolean context now raises an error hinting at the proper usage. It is apparently common for users to mistakenly use `pytest.approx` like this: ```{.sourceCode .python} assert pytest.approx(actual, expected) ``` While the correct usage is: ```{.sourceCode .python} assert actual == pytest.approx(expected) ``` The new error message helps catch those mistakes. - [#​9277](https://togithub.com/pytest-dev/pytest/issues/9277): The `pytest.Instance` collector type has been removed. Importing `pytest.Instance` or `_pytest.python.Instance` returns a dummy type and emits a deprecation warning. See `instance-collector-deprecation`{.interpreted-text role="ref"} for details. - [#​9308](https://togithub.com/pytest-dev/pytest/issues/9308): **PytestRemovedIn7Warning deprecation warnings are now errors by default.** Following our plan to remove deprecated features with as little disruption as possible, all warnings of type `PytestRemovedIn7Warning` now generate errors instead of warning messages by default. **The affected features will be effectively removed in pytest 7.1**, so please consult the `deprecations`{.interpreted-text role="ref"} section in the docs for directions on how to update existing code. In the pytest `7.0.X` series, it is possible to change the errors back into warnings as a stopgap measure by adding this to your `pytest.ini` file: ```{.sourceCode .ini} [pytest] filterwarnings = ignore::pytest.PytestRemovedIn7Warning ``` But this will stop working when pytest `7.1` is released. **If you have concerns** about the removal of a specific feature, please add a comment to `9308`{.interpreted-text role="issue"}. ## Deprecations - [#​7259](https://togithub.com/pytest-dev/pytest/issues/7259): `py.path.local` arguments for hooks have been deprecated. See `the deprecation note `{.interpreted-text role="ref"} for full details. `py.path.local` arguments to Node constructors have been deprecated. See `the deprecation note `{.interpreted-text role="ref"} for full details. ::: {.note} ::: {.admonition-title} Note ::: The name of the `~_pytest.nodes.Node`{.interpreted-text role="class"} arguments and attributes (the new attribute being `path`) is **the opposite** of the situation for hooks (the old argument being `path`). This is an unfortunate artifact due to historical reasons, which should be resolved in future versions as we slowly get rid of the `py`{.interpreted-text role="pypi"} dependency (see `9283`{.interpreted-text role="issue"} for a longer discussion). ::: - [#​7469](https://togithub.com/pytest-dev/pytest/issues/7469): Directly constructing the following classes is now deprecated: - `_pytest.mark.structures.Mark` - `_pytest.mark.structures.MarkDecorator` - `_pytest.mark.structures.MarkGenerator` - `_pytest.python.Metafunc` - `_pytest.runner.CallInfo` - `_pytest._code.ExceptionInfo` - `_pytest.config.argparsing.Parser` - `_pytest.config.argparsing.OptionGroup` - `_pytest.pytester.HookRecorder` These constructors have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 8. - [#​8242](https://togithub.com/pytest-dev/pytest/issues/8242): Raising `unittest.SkipTest`{.interpreted-text role="class"} to skip collection of tests during the pytest collection phase is deprecated. Use `pytest.skip`{.interpreted-text role="func"} instead. Note: This deprecation only relates to using `unittest.SkipTest`{.interpreted-text role="class"} during test collection. You are probably not doing that. Ordinary usage of `unittest.SkipTest`{.interpreted-text role="class"} / `unittest.TestCase.skipTest`{.interpreted-text role="meth"} / `unittest.skip`{.interpreted-text role="func"} in unittest test cases is fully supported. - [#​8315](https://togithub.com/pytest-dev/pytest/issues/8315): Several behaviors of `Parser.addoption `{.interpreted-text role="meth"} are now scheduled for removal in pytest 8 (deprecated since pytest 2.4.0): - `parser.addoption(..., help=".. %default ..")` - use `%(default)s` instead. - `parser.addoption(..., type="int/string/float/complex")` - use `type=int` etc. instead. - [#​8447](https://togithub.com/pytest-dev/pytest/issues/8447): Defining a custom pytest node type which is both an `pytest.Item `{.interpreted-text role="class"} and a `pytest.Collector `{.interpreted-text role="class"} (e.g. `pytest.File `{.interpreted-text role="class"}) now issues a warning. It was never sanely supported and triggers hard to debug errors. See `the deprecation note `{.interpreted-text role="ref"} for full details. - [#​8592](https://togithub.com/pytest-dev/pytest/issues/8592): `pytest_cmdline_preparse`{.interpreted-text role="hook"} has been officially deprecated. It will be removed in a future release. Use `pytest_load_initial_conftests`{.interpreted-text role="hook"} instead. See `the deprecation note `{.interpreted-text role="ref"} for full details. - [#​8645](https://togithub.com/pytest-dev/pytest/issues/8645): `pytest.warns(None) `{.interpreted-text role="func"} is now deprecated because many people used it to mean "this code does not emit warnings", but it actually had the effect of checking that the code emits at least one warning of any type - like `pytest.warns()` or `pytest.warns(Warning)`. - [#​8948](https://togithub.com/pytest-dev/pytest/issues/8948): `pytest.skip(msg=...) `{.interpreted-text role="func"}, `pytest.fail(msg=...) `{.interpreted-text role="func"} and `pytest.exit(msg=...) `{.interpreted-text role="func"} signatures now accept a `reason` argument instead of `msg`. Using `msg` still works, but is deprecated and will be removed in a future release. This was changed for consistency with `pytest.mark.skip `{.interpreted-text role="func"} and `pytest.mark.xfail `{.interpreted-text role="func"} which both accept `reason` as an argument. - [#​8174](https://togithub.com/pytest-dev/pytest/issues/8174): The following changes have been made to types reachable through `pytest.ExceptionInfo.traceback`{.interpreted-text role="attr"}: - The `path` property of `_pytest.code.Code` returns `Path` instead of `py.path.local`. - The `path` property of `_pytest.code.TracebackEntry` returns `Path` instead of `py.path.local`. There was no deprecation period for this change (sorry!). ## Features - [#​5196](https://togithub.com/pytest-dev/pytest/issues/5196): Tests are now ordered by definition order in more cases. In a class hierarchy, tests from base classes are now consistently ordered before tests defined on their subclasses (reverse MRO order). - [#​7132](https://togithub.com/pytest-dev/pytest/issues/7132): Added two environment variables `PYTEST_THEME`{.interpreted-text role="envvar"} and `PYTEST_THEME_MODE`{.interpreted-text role="envvar"} to let the users customize the pygments theme used. - [#​7259](https://togithub.com/pytest-dev/pytest/issues/7259): Added `cache.mkdir() `{.interpreted-text role="meth"}, which is similar to the existing `cache.makedir() `{.interpreted-text role="meth"}, but returns a `pathlib.Path`{.interpreted-text role="class"} instead of a legacy `py.path.local`. Added a `paths` type to `parser.addini() `{.interpreted-text role="meth"}, as in `parser.addini("mypaths", "my paths", type="paths")`, which is similar to the existing `pathlist`, but returns a list of `pathlib.Path`{.interpreted-text role="class"} instead of legacy `py.path.local`. - [#​7469](https://togithub.com/pytest-dev/pytest/issues/7469): The types of objects used in pytest's API are now exported so they may be used in type annotations. The newly-exported types are: - `pytest.Config` for `Config `{.interpreted-text role="class"}. - `pytest.Mark` for `marks `{.interpreted-text role="class"}. - `pytest.MarkDecorator` for `mark decorators `{.interpreted-text role="class"}. - `pytest.MarkGenerator` for the `pytest.mark `{.interpreted-text role="class"} singleton. - `pytest.Metafunc` for the `metafunc `{.interpreted-text role="class"} argument to the `pytest_generate_tests`{.interpreted-text role="hook"} hook. - `pytest.CallInfo` for the `CallInfo `{.interpreted-text role="class"} type passed to various hooks. - `pytest.PytestPluginManager` for `PytestPluginManager `{.interpreted-text role="class"}. - `pytest.ExceptionInfo` for the `ExceptionInfo `{.interpreted-text role="class"} type returned from `pytest.raises`{.interpreted-text role="func"} and passed to various hooks. - `pytest.Parser` for the `Parser `{.interpreted-text role="class"} type passed to the `pytest_addoption`{.interpreted-text role="hook"} hook. - `pytest.OptionGroup` for the `OptionGroup `{.interpreted-text role="class"} type returned from the `parser.addgroup `{.interpreted-text role="func"} method. - `pytest.HookRecorder` for the `HookRecorder `{.interpreted-text role="class"} type returned from `~pytest.Pytester`{.interpreted-text role="class"}. - `pytest.RecordedHookCall` for the `RecordedHookCall `{.interpreted-text role="class"} type returned from `~pytest.HookRecorder`{.interpreted-text role="class"}. - `pytest.RunResult` for the `RunResult `{.interpreted-text role="class"} type returned from `~pytest.Pytester`{.interpreted-text role="class"}. - `pytest.LineMatcher` for the `LineMatcher `{.interpreted-text role="class"} type used in `~pytest.RunResult`{.interpreted-text role="class"} and others. - `pytest.TestReport` for the `TestReport `{.interpreted-text role="class"} type used in various hooks. - `pytest.CollectReport` for the `CollectReport `{.interpreted-text role="class"} type used in various hooks. Constructing most of them directly is not supported; they are only meant for use in type annotations. Doing so will emit a deprecation warning, and may become a hard-error in pytest 8.0. Subclassing them is also not supported. This is not currently enforced at runtime, but is detected by type-checkers such as mypy. - [#​7856](https://togithub.com/pytest-dev/pytest/issues/7856): `--import-mode=importlib `{.interpreted-text role="ref"} now works with features that depend on modules being on :py`sys.modules`{.interpreted-text role="data"}, such as `pickle`{.interpreted-text role="mod"} and `dataclasses`{.interpreted-text role="mod"}. - [#​8144](https://togithub.com/pytest-dev/pytest/issues/8144): The following hooks now receive an additional `pathlib.Path` argument, equivalent to an existing `py.path.local` argument: - `pytest_ignore_collect`{.interpreted-text role="hook"} - The `collection_path` parameter (equivalent to existing `path` parameter). - `pytest_collect_file`{.interpreted-text role="hook"} - The `file_path` parameter (equivalent to existing `path` parameter). - `pytest_pycollect_makemodule`{.interpreted-text role="hook"} - The `module_path` parameter (equivalent to existing `path` parameter). - `pytest_report_header`{.interpreted-text role="hook"} - The `start_path` parameter (equivalent to existing `startdir` parameter). - `pytest_report_collectionfinish`{.interpreted-text role="hook"} - The `start_path` parameter (equivalent to existing `startdir` parameter). ::: {.note} ::: {.admonition-title} Note ::: The name of the `~_pytest.nodes.Node`{.interpreted-text role="class"} arguments and attributes (the new attribute being `path`) is **the opposite** of the situation for hooks (the old argument being `path`). This is an unfortunate artifact due to historical reasons, which should be resolved in future versions as we slowly get rid of the `py`{.interpreted-text role="pypi"} dependency (see `9283`{.interpreted-text role="issue"} for a longer discussion). ::: - [#​8251](https://togithub.com/pytest-dev/pytest/issues/8251): Implement `Node.path` as a `pathlib.Path`. Both the old `fspath` and this new attribute gets set no matter whether `path` or `fspath` (deprecated) is passed to the constructor. It is a replacement for the `fspath` attribute (which represents the same path as `py.path.local`). While `fspath` is not deprecated yet due to the ongoing migration of methods like `~_pytest.Item.reportinfo`{.interpreted-text role="meth"}, we expect to deprecate it in a future release. ::: {.note} ::: {.admonition-title} Note ::: The name of the `~_pytest.nodes.Node`{.interpreted-text role="class"} arguments and attributes (the new attribute being `path`) is **the opposite** of the situation for hooks (the old argument being `path`). This is an unfortunate artifact due to historical reasons, which should be resolved in future versions as we slowly get rid of the `py`{.interpreted-text role="pypi"} dependency (see `9283`{.interpreted-text role="issue"} for a longer discussion). ::: - [#​8421](https://togithub.com/pytest-dev/pytest/issues/8421): `pytest.approx`{.interpreted-text role="func"} now works on `~decimal.Decimal`{.interpreted-text role="class"} within mappings/dicts and sequences/lists. - [#​8606](https://togithub.com/pytest-dev/pytest/issues/8606): pytest invocations with `--fixtures-per-test` and `--fixtures` have been enriched with: - Fixture location path printed with the fixture name. - First section of the fixture's docstring printed under the fixture name. - Whole of fixture's docstring printed under the fixture name using `--verbose` option. - [#​8761](https://togithub.com/pytest-dev/pytest/issues/8761): New `version-tuple`{.interpreted-text role="ref"} attribute, which makes it simpler for users to do something depending on the pytest version (such as declaring hooks which are introduced in later versions). - [#​8789](https://togithub.com/pytest-dev/pytest/issues/8789): Switch TOML parser from `toml` to `tomli` for TOML v1.0.0 support in `pyproject.toml`. - [#​8920](https://togithub.com/pytest-dev/pytest/issues/8920): Added `pytest.Stash`{.interpreted-text role="class"}, a facility for plugins to store their data on `~pytest.Config`{.interpreted-text role="class"} and `~_pytest.nodes.Node`{.interpreted-text role="class"}s in a type-safe and conflict-free manner. See `plugin-stash`{.interpreted-text role="ref"} for details. - [#​8953](https://togithub.com/pytest-dev/pytest/issues/8953): `RunResult <_pytest.pytester.RunResult>`{.interpreted-text role="class"} method `assert_outcomes <_pytest.pytester.RunResult.assert_outcomes>`{.interpreted-text role="meth"} now accepts a `warnings` argument to assert the total number of warnings captured. - [#​8954](https://togithub.com/pytest-dev/pytest/issues/8954): `--debug` flag now accepts a `str`{.interpreted-text role="class"} file to route debug logs into, remains defaulted to \[pytestdebug.log]{.title-ref}. - [#​9023](https://togithub.com/pytest-dev/pytest/issues/9023): Full diffs are now always shown for equality assertions of iterables when \[CI]{.title-ref} or `BUILD_NUMBER` is found in the environment, even when `-v` isn't used. - [#​9113](https://togithub.com/pytest-dev/pytest/issues/9113): `RunResult <_pytest.pytester.RunResult>`{.interpreted-text role="class"} method `assert_outcomes <_pytest.pytester.RunResult.assert_outcomes>`{.interpreted-text role="meth"} now accepts a `deselected` argument to assert the total number of deselected tests. - [#​9114](https://togithub.com/pytest-dev/pytest/issues/9114): Added `pythonpath`{.interpreted-text role="confval"} setting that adds listed paths to `sys.path`{.interpreted-text role="data"} for the duration of the test session. If you currently use the pytest-pythonpath or pytest-srcpaths plugins, you should be able to replace them with built-in \[pythonpath]{.title-ref} setting. ## Improvements - [#​7480](https://togithub.com/pytest-dev/pytest/issues/7480): A deprecation scheduled to be removed in a major version X (e.g. pytest 7, 8, 9, ...) now uses warning category \[PytestRemovedInXWarning]{.title-ref}, a subclass of `~pytest.PytestDeprecationWarning`{.interpreted-text role="class"}, instead of `PytestDeprecationWarning`{.interpreted-text role="class"} directly. See `backwards-compatibility`{.interpreted-text role="ref"} for more details. - [#​7864](https://togithub.com/pytest-dev/pytest/issues/7864): Improved error messages when parsing warning filters. Previously pytest would show an internal traceback, which besides being ugly sometimes would hide the cause of the problem (for example an `ImportError` while importing a specific warning type). - [#​8335](https://togithub.com/pytest-dev/pytest/issues/8335): Improved `pytest.approx`{.interpreted-text role="func"} assertion messages for sequences of numbers. The assertion messages now dumps a table with the index and the error of each diff. Example: > assert [1, 2, 3, 4] == pytest.approx([1, 3, 3, 5]) E assert comparison failed for 2 values: E Index | Obtained | Expected E 1 | 2 | 3 +- 3.0e-06 E 3 | 4 | 5 +- 5.0e-06 - [#​8403](https://togithub.com/pytest-dev/pytest/issues/8403): By default, pytest will truncate long strings in assert errors so they don't clutter the output too much, currently at `240` characters by default. However, in some cases the longer output helps, or is even crucial, to diagnose a failure. Using `-v` will now increase the truncation threshold to `2400` characters, and `-vv` or higher will disable truncation entirely. - [#​8509](https://togithub.com/pytest-dev/pytest/issues/8509): Fixed issue where `unittest.TestCase.setUpClass`{.interpreted-text role="meth"} is not called when a test has \[/]{.title-ref} in its name since pytest 6.2.0. This refers to the path part in pytest node IDs, e.g. `TestClass::test_it` in the node ID `tests/test_file.py::TestClass::test_it`. Now, instead of assuming that the test name does not contain `/`, it is assumed that test path does not contain `::`. We plan to hopefully make both of these work in the future. - [#​8803](https://togithub.com/pytest-dev/pytest/issues/8803): It is now possible to add colors to custom log levels on cli log. By using `add_color_level <_pytest.logging.add_color_level>`{.interpreted-text role="func"} from a `pytest_configure` hook, colors can be added: logging_plugin = config.pluginmanager.get_plugin('logging-plugin') logging_plugin.log_cli_handler.formatter.add_color_level(logging.INFO, 'cyan') logging_plugin.log_cli_handler.formatter.add_color_level(logging.SPAM, 'blue') See `log_colors`{.interpreted-text role="ref"} for more information. - [#​8822](https://togithub.com/pytest-dev/pytest/issues/8822): When showing fixture paths in \[--fixtures]{.title-ref} or \[--fixtures-by-test]{.title-ref}, fixtures coming from pytest itself now display an elided path, rather than the full path to the file in the \[site-packages]{.title-ref} directory. - [#​8898](https://togithub.com/pytest-dev/pytest/issues/8898): Complex numbers are now treated like floats and integers when generating parameterization IDs. - [#​9062](https://togithub.com/pytest-dev/pytest/issues/9062): `--stepwise-skip` now implicitly enables `--stepwise` and can be used on its own. - [#​9205](https://togithub.com/pytest-dev/pytest/issues/9205): `pytest.Cache.set`{.interpreted-text role="meth"} now preserves key order when saving dicts. ## Bug Fixes - [#​7124](https://togithub.com/pytest-dev/pytest/issues/7124): Fixed an issue where `__main__.py` would raise an `ImportError` when `--doctest-modules` was provided. - [#​8061](https://togithub.com/pytest-dev/pytest/issues/8061): Fixed failing `staticmethod` test cases if they are inherited from a parent test class. - [#​8192](https://togithub.com/pytest-dev/pytest/issues/8192): `testdir.makefile` now silently accepts values which don't start with `.` to maintain backward compatibility with older pytest versions. `pytester.makefile` now issues a clearer error if the `.` is missing in the `ext` argument. - [#​8258](https://togithub.com/pytest-dev/pytest/issues/8258): Fixed issue where pytest's `faulthandler` support would not dump traceback on crashes if the `faulthandler`{.interpreted-text role="mod"} module was already enabled during pytest startup (using `python -X dev -m pytest` for example). - [#​8317](https://togithub.com/pytest-dev/pytest/issues/8317): Fixed an issue where illegal directory characters derived from `getpass.getuser()` raised an `OSError`. - [#​8367](https://togithub.com/pytest-dev/pytest/issues/8367): Fix `Class.from_parent` so it forwards extra keyword arguments to the constructor. - [#​8377](https://togithub.com/pytest-dev/pytest/issues/8377): The test selection options `pytest -k` and `pytest -m` now support matching names containing forward slash (`/`) characters. - [#​8384](https://togithub.com/pytest-dev/pytest/issues/8384): The `@pytest.mark.skip` decorator now correctly handles its arguments. When the `reason` argument is accidentally given both positional and as a keyword (e.g. because it was confused with `skipif`), a `TypeError` now occurs. Before, such tests were silently skipped, and the positional argument ignored. Additionally, `reason` is now documented correctly as positional or keyword (rather than keyword-only). - [#​8394](https://togithub.com/pytest-dev/pytest/issues/8394): Use private names for internal fixtures that handle classic setup/teardown so that they don't show up with the default `--fixtures` invocation (but they still show up with `--fixtures -v`). - [#​8456](https://togithub.com/pytest-dev/pytest/issues/8456): The `required_plugins`{.interpreted-text role="confval"} config option now works correctly when pre-releases of plugins are installed, rather than falsely claiming that those plugins aren't installed at all. - [#​8464](https://togithub.com/pytest-dev/pytest/issues/8464): `-c ` now also properly defines `rootdir` as the directory that contains ``. - [#​8503](https://togithub.com/pytest-dev/pytest/issues/8503): `pytest.MonkeyPatch.syspath_prepend`{.interpreted-text role="meth"} no longer fails when `setuptools` is not installed. It now only calls `pkg_resources.fixup_namespace_packages`{.interpreted-text role="func"} if `pkg_resources` was previously imported, because it is not needed otherwise. - [#​8548](https://togithub.com/pytest-dev/pytest/issues/8548): Introduce fix to handle precision width in `log-cli-format` in turn to fix output coloring for certain formats. - [#​8796](https://togithub.com/pytest-dev/pytest/issues/8796): Fixed internal error when skipping doctests. - [#​8983](https://togithub.com/pytest-dev/pytest/issues/8983): The test selection options `pytest -k` and `pytest -m` now support matching names containing backslash (\[\\]{.title-ref}) characters. Backslashes are treated literally, not as escape characters (the values being matched against are already escaped). - [#​8990](https://togithub.com/pytest-dev/pytest/issues/8990): Fix \[pytest -vv]{.title-ref} crashing with an internal exception \[AttributeError: 'str' object has no attribute 'relative_to']{.title-ref} in some cases. - [#​9077](https://togithub.com/pytest-dev/pytest/issues/9077): Fixed confusing error message when `request.fspath` / `request.path` was accessed from a session-scoped fixture. - [#​9131](https://togithub.com/pytest-dev/pytest/issues/9131): Fixed the URL used by `--pastebin` to use [bpa.st](http://bpa.st). - [#​9163](https://togithub.com/pytest-dev/pytest/issues/9163): The end line number and end column offset are now properly set for rewritten assert statements. - [#​9169](https://togithub.com/pytest-dev/pytest/issues/9169): Support for the `files` API from `importlib.resources` within rewritten files. - [#​9272](https://togithub.com/pytest-dev/pytest/issues/9272): The nose compatibility module-level fixtures \[setup()]{.title-ref} and \[teardown()]{.title-ref} are now only called once per module, instead of for each test function. They are now called even if object-level \[setup]{.title-ref}/\[teardown]{.title-ref} is defined. ## Improved Documentation - [#​4320](https://togithub.com/pytest-dev/pytest/issues/4320): Improved docs for \[pytester.copy_example]{.title-ref}. - [#​5105](https://togithub.com/pytest-dev/pytest/issues/5105): Add automatically generated `plugin-list`{.interpreted-text role="ref"}. The list is updated on a periodic schedule. - [#​8337](https://togithub.com/pytest-dev/pytest/issues/8337): Recommend [numpy.testing](https://numpy.org/doc/stable/reference/routines.testing.html) module on `pytest.approx`{.interpreted-text role="func"} documentation. - [#​8655](https://togithub.com/pytest-dev/pytest/issues/8655): Help text for `--pdbcls` more accurately reflects the option's behavior. - [#​9210](https://togithub.com/pytest-dev/pytest/issues/9210): Remove incorrect docs about `confcutdir` being a configuration option: it can only be set through the `--confcutdir` command-line option. - [#​9242](https://togithub.com/pytest-dev/pytest/issues/9242): Upgrade readthedocs configuration to use a [newer Ubuntu version](https://blog.readthedocs.com/new-build-specification/)\` with better unicode support for PDF docs. - [#​9341](https://togithub.com/pytest-dev/pytest/issues/9341): Various methods commonly used for `non-python tests`{.interpreted-text role="ref"} are now correctly documented in the reference docs. They were undocumented previously. ## Trivial/Internal Changes - [#​8133](https://togithub.com/pytest-dev/pytest/issues/8133): Migrate to `setuptools_scm` 6.x to use `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST` for more robust release tooling. - [#​8174](https://togithub.com/pytest-dev/pytest/issues/8174): The following changes have been made to internal pytest types/functions: - The `_pytest.code.getfslineno()` function returns `Path` instead of `py.path.local`. - The `_pytest.python.path_matches_patterns()` function takes `Path` instead of `py.path.local`. - The `_pytest._code.Traceback.cut()` function accepts any `os.PathLike[str]`, not just `py.path.local`. - [#​8248](https://togithub.com/pytest-dev/pytest/issues/8248): Internal Restructure: let `python.PyObjMixin` inherit from `nodes.Node` to carry over typing information. - [#​8432](https://togithub.com/pytest-dev/pytest/issues/8432): Improve error message when `pytest.skip`{.interpreted-text role="func"} is used at module level without passing \[allow_module_level=True]{.title-ref}. - [#​8818](https://togithub.com/pytest-dev/pytest/issues/8818): Ensure `regendoc` opts out of `TOX_ENV` cachedir selection to ensure independent example test runs. - [#​8913](https://togithub.com/pytest-dev/pytest/issues/8913): The private `CallSpec2._arg2scopenum` attribute has been removed after an internal refactoring. - [#​8967](https://togithub.com/pytest-dev/pytest/issues/8967): `pytest_assertion_pass`{.interpreted-text role="hook"} is no longer considered experimental and future changes to it will be considered more carefully. - [#​9202](https://togithub.com/pytest-dev/pytest/issues/9202): Add github action to upload coverage report to codecov instead of bash uploader. - [#​9225](https://togithub.com/pytest-dev/pytest/issues/9225): Changed the command used to create sdist and wheel artifacts: using the build package instead of setup.py. - [#​9351](https://togithub.com/pytest-dev/pytest/issues/9351): Correct minor typos in doc/en/example/special.rst.
--- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Renovate will not automatically rebase this PR, because other commits have been found. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, click this checkbox. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/python-compute). --- compute/compute/snippets/requirements-test.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/snippets/requirements-test.txt index 54604cd4a3a..016b782ca3a 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/snippets/requirements-test.txt @@ -1,4 +1,4 @@ -pytest==6.2.5 +pytest==7.0.1 flaky==3.7.0 -google-cloud-storage==2.0.0; python_version == '3.6' +google-cloud-storage==2.1.0; python_version == '3.6' google-cloud-storage==2.1.0; python_version >= '3.7' From 337cb2f3057f1df57549b0a7a0259419497f0ee7 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Thu, 17 Feb 2022 13:34:01 +0100 Subject: [PATCH 060/113] chore(samples): Fixing docstring (#222) --- compute/compute/snippets/sample_start_stop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/sample_start_stop.py b/compute/compute/snippets/sample_start_stop.py index 66218c74e1b..c4cfc620a3e 100644 --- a/compute/compute/snippets/sample_start_stop.py +++ b/compute/compute/snippets/sample_start_stop.py @@ -130,7 +130,7 @@ def stop_instance(project_id: str, zone: str, instance_name: str): # [START compute_reset_instance] def reset_instance(project_id: str, zone: str, instance_name: str): """ - Resets a stopped Google Compute Engine instance (with unencrypted disks). + Resets a running Google Compute Engine instance (with unencrypted disks). Args: project_id: project ID or project number of the Cloud project your instance belongs to. From e8a36cc6fbd17adfab9ea300be27fc8ba1da6cbe Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Mon, 21 Feb 2022 14:28:20 +0100 Subject: [PATCH 061/113] chore(samples): Snippet Generating System (part 1) (#199) * chore(samples): Snippet Generating System Preparing the SGS (Snippet Generating System) script to handle the burden of duplicated code in the samples. Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- compute/compute/README.md | 53 +++ compute/compute/{snippets => }/noxfile.py | 0 compute/compute/noxfile_config.py | 18 + .../{snippets => }/requirements-test.txt | 2 +- compute/compute/requirements.txt | 3 + compute/compute/sgs.py | 340 ++++++++++++++++++ .../ingredients/ingredient1.pytest | 30 ++ .../ingredients/ingredient2.pytest | 31 ++ .../output/experimental_recipe.pytest | 60 ++++ .../recipes/experimental_recipe.pytest | 29 ++ compute/compute/snippets/README.md | 2 +- compute/compute/snippets/noxfile_config.py | 18 - compute/compute/snippets/requirements.txt | 1 - compute/compute/test_sgs.py | 32 ++ 14 files changed, 598 insertions(+), 21 deletions(-) create mode 100644 compute/compute/README.md rename compute/compute/{snippets => }/noxfile.py (100%) create mode 100644 compute/compute/noxfile_config.py rename compute/compute/{snippets => }/requirements-test.txt (60%) create mode 100644 compute/compute/requirements.txt create mode 100644 compute/compute/sgs.py create mode 100644 compute/compute/sgs_test_fixtures/ingredients/ingredient1.pytest create mode 100644 compute/compute/sgs_test_fixtures/ingredients/ingredient2.pytest create mode 100644 compute/compute/sgs_test_fixtures/output/experimental_recipe.pytest create mode 100644 compute/compute/sgs_test_fixtures/recipes/experimental_recipe.pytest delete mode 100644 compute/compute/snippets/noxfile_config.py delete mode 100644 compute/compute/snippets/requirements.txt create mode 100644 compute/compute/test_sgs.py diff --git a/compute/compute/README.md b/compute/compute/README.md new file mode 100644 index 00000000000..cc715bec0c3 --- /dev/null +++ b/compute/compute/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/compute/snippets/noxfile.py b/compute/compute/noxfile.py similarity index 100% rename from compute/compute/snippets/noxfile.py rename to compute/compute/noxfile.py diff --git a/compute/compute/noxfile_config.py b/compute/compute/noxfile_config.py new file mode 100644 index 00000000000..33170d346c8 --- /dev/null +++ b/compute/compute/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_sample_default_values.py require separate projects to not interfere with each other. + "gcloud_project_env": "BUILD_SPECIFIC_GCLOUD_PROJECT", +} diff --git a/compute/compute/snippets/requirements-test.txt b/compute/compute/requirements-test.txt similarity index 60% rename from compute/compute/snippets/requirements-test.txt rename to compute/compute/requirements-test.txt index 016b782ca3a..5737e358445 100644 --- a/compute/compute/snippets/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -1,4 +1,4 @@ pytest==7.0.1 flaky==3.7.0 google-cloud-storage==2.1.0; python_version == '3.6' -google-cloud-storage==2.1.0; python_version >= '3.7' +google-cloud-storage==2.1.0; python_version >= '3.7' \ No newline at end of file diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt new file mode 100644 index 00000000000..23ea27c39c0 --- /dev/null +++ b/compute/compute/requirements.txt @@ -0,0 +1,3 @@ +isort==5.10.1 +black==22.1.0 +google-cloud-compute==1.0.0 \ No newline at end of file diff --git a/compute/compute/sgs.py b/compute/compute/sgs.py new file mode 100644 index 00000000000..f7d4f43c883 --- /dev/null +++ b/compute/compute/sgs.py @@ -0,0 +1,340 @@ +# 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("sgs_test_fixtures/output") +INGREDIENTS_PATH = Path("sgs_test_fixtures/ingredients") +RECIPES_PATH = Path("sgs_test_fixtures/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__/.*"), +) + + +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: + 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(): + 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] = 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(exist_ok=True) + + output_path = output_dir / recipe_path.relative_to(recipes_path) + output_path.parent.mkdir(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, rendered, 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/compute/sgs_test_fixtures/ingredients/ingredient1.pytest b/compute/compute/sgs_test_fixtures/ingredients/ingredient1.pytest new file mode 100644 index 00000000000..c547d737447 --- /dev/null +++ b/compute/compute/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/compute/sgs_test_fixtures/ingredients/ingredient2.pytest b/compute/compute/sgs_test_fixtures/ingredients/ingredient2.pytest new file mode 100644 index 00000000000..3b50b3b62b3 --- /dev/null +++ b/compute/compute/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/compute/sgs_test_fixtures/output/experimental_recipe.pytest b/compute/compute/sgs_test_fixtures/output/experimental_recipe.pytest new file mode 100644 index 00000000000..f8846776f88 --- /dev/null +++ b/compute/compute/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/compute/sgs_test_fixtures/recipes/experimental_recipe.pytest b/compute/compute/sgs_test_fixtures/recipes/experimental_recipe.pytest new file mode 100644 index 00000000000..6f384ed4f79 --- /dev/null +++ b/compute/compute/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/compute/snippets/README.md b/compute/compute/snippets/README.md index 966f4dfe919..64d0ec565fd 100644 --- a/compute/compute/snippets/README.md +++ b/compute/compute/snippets/README.md @@ -19,7 +19,7 @@ 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 +pip install -r ../requirements.txt ``` ### Run the demo diff --git a/compute/compute/snippets/noxfile_config.py b/compute/compute/snippets/noxfile_config.py deleted file mode 100644 index 5794d5fe1ed..00000000000 --- a/compute/compute/snippets/noxfile_config.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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. - -TEST_CONFIG_OVERRIDE = { - # Tests in test_sample_default_values.py require separate projects to not interfere with each other. - "gcloud_project_env": "BUILD_SPECIFIC_GCLOUD_PROJECT", -} diff --git a/compute/compute/snippets/requirements.txt b/compute/compute/snippets/requirements.txt deleted file mode 100644 index 16e20f3778a..00000000000 --- a/compute/compute/snippets/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -google-cloud-compute==1.0.0 \ No newline at end of file diff --git a/compute/compute/test_sgs.py b/compute/compute/test_sgs.py new file mode 100644 index 00000000000..f1c1d71ae14 --- /dev/null +++ b/compute/compute/test_sgs.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. +from argparse import Namespace +import glob +from pathlib import Path +import tempfile + +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, FIXTURE_RECIPES) + 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() From 098ed988a08ad12c167ab524366ebaa54e0e128c Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 23 Feb 2022 17:52:56 +0100 Subject: [PATCH 062/113] chore(samples): Adding samples for custom hostname (#221) * chore(samples): Adding samples for custom hostname * chore(samples): Changing zone for tests, as europe-central2-c seems to have capcity issues with e2 instances. --- .../snippets/sample_custom_hostname.py | 118 ++++++++++++++++++ .../snippets/test_sample_custom_hostname.py | 51 ++++++++ .../test_sample_instance_from_template.py | 4 +- 3 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 compute/compute/snippets/sample_custom_hostname.py create mode 100644 compute/compute/snippets/test_sample_custom_hostname.py diff --git a/compute/compute/snippets/sample_custom_hostname.py b/compute/compute/snippets/sample_custom_hostname.py new file mode 100644 index 00000000000..8d732f59490 --- /dev/null +++ b/compute/compute/snippets/sample_custom_hostname.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. + +# [START compute_instances_create_custom_hostname] +import sys + + +# [START compute_instances_get_hostname] +from google.cloud import compute_v1 + +# [END compute_instances_get_hostname] +# [END compute_instances_create_custom_hostname] + + +# [START compute_instances_create_custom_hostname] +def create_instance( + project_id: str, zone: str, instance_name: str, hostname: str, +) -> 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 you want to use. For example: “us-west3-b” + instance_name: name of the new virtual machine. + hostname: Custom hostname of the new VM instance. + Custom hostnames must conform to RFC 1035 requirements for valid hostnames. + + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + operation_client = compute_v1.ZoneOperationsClient() + + # Describe 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-10" + ) + initialize_params.disk_size_gb = 10 + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + disk.type_ = "PERSISTENT" + + # Use the default VPC network. + network_interface = compute_v1.NetworkInterface() + network_interface.name = "default" + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.name = instance_name + instance.disks = [disk] + instance.machine_type = f"zones/{zone}/machineTypes/e2-small" + instance.network_interfaces = [network_interface] + + # Custom hostnames are not resolved by the automatically created records + # provided by Compute Engine internal DNS. + # You must manually configure the DNS record for your custom hostname. + instance.hostname = hostname + + # 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +# [END compute_instances_create_custom_hostname] + + +# [START compute_instances_get_hostname] +def get_instance_hostname(project_id: str, zone: str, instance_name: str) -> str: + """ + Get the hostname set for 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 you want to check. + + Returns: + The hostname of given 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/compute/snippets/test_sample_custom_hostname.py b/compute/compute/snippets/test_sample_custom_hostname.py new file mode 100644 index 00000000000..80666c45937 --- /dev/null +++ b/compute/compute/snippets/test_sample_custom_hostname.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 quickstart import delete_instance +from sample_custom_hostname import create_instance +from sample_custom_hostname import get_instance_hostname + +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_delete_protection(autodelete_instance_name, random_hostname): + instance = create_instance( + PROJECT, INSTANCE_ZONE, autodelete_instance_name, random_hostname + ) + assert instance.name == autodelete_instance_name + assert instance.hostname == random_hostname + assert ( + get_instance_hostname(PROJECT, INSTANCE_ZONE, autodelete_instance_name) + == random_hostname + ) diff --git a/compute/compute/snippets/test_sample_instance_from_template.py b/compute/compute/snippets/test_sample_instance_from_template.py index 20f1eb7cf5c..c6a2f0f62e6 100644 --- a/compute/compute/snippets/test_sample_instance_from_template.py +++ b/compute/compute/snippets/test_sample_instance_from_template.py @@ -25,7 +25,7 @@ PROJECT = google.auth.default()[1] -INSTANCE_ZONE = "us-central1-b" +INSTANCE_ZONE = "europe-north1-c" @pytest.fixture @@ -46,7 +46,7 @@ def instance_template(): template = compute_v1.InstanceTemplate() template.name = "test-template-" + uuid.uuid4().hex[:10] template.properties.disks = [disk] - template.properties.machine_type = "e2-standard-4" + template.properties.machine_type = "n1-standard-4" template.properties.network_interfaces = [network_interface] template_client = compute_v1.InstanceTemplatesClient() From 2625f39022eaa388dcee42e193845f99485ec0aa Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 2 Mar 2022 17:15:49 +0100 Subject: [PATCH 063/113] chore(samples): Typo fix (#230) --- compute/compute/snippets/sample_start_stop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/sample_start_stop.py b/compute/compute/snippets/sample_start_stop.py index c4cfc620a3e..36fe54b9995 100644 --- a/compute/compute/snippets/sample_start_stop.py +++ b/compute/compute/snippets/sample_start_stop.py @@ -38,7 +38,7 @@ def start_instance(project_id: str, zone: str, instance_name: str): 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_name: name of the instance you want to start. """ instance_client = compute_v1.InstancesClient() op_client = compute_v1.ZoneOperationsClient() From 5b88f855d428b3ef6bf5a77e3cd57ae3704140ed Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 4 Mar 2022 13:12:13 -0500 Subject: [PATCH 064/113] chore: Adding support for pytest-xdist and pytest-parallel (#235) Source-Link: https://github.com/googleapis/synthtool/commit/82f5cb283efffe96e1b6cd634738e0e7de2cd90a Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:5d8da01438ece4021d135433f2cf3227aa39ef0eaccc941d62aa35e6902832ae Co-authored-by: Owl Bot Co-authored-by: Maciej Strzelczyk --- compute/compute/noxfile.py | 78 +++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/compute/compute/noxfile.py b/compute/compute/noxfile.py index 20cdfc62013..85f5836dba3 100644 --- a/compute/compute/noxfile.py +++ b/compute/compute/noxfile.py @@ -188,42 +188,52 @@ def _session_tests( # check for presence of tests test_list = glob.glob("*_test.py") + glob.glob("test_*.py") test_list.extend(glob.glob("tests")) + if len(test_list) == 0: print("No tests found, skipping directory.") - else: - if TEST_CONFIG["pip_version_override"]: - pip_version = TEST_CONFIG["pip_version_override"] - session.install(f"pip=={pip_version}") - """Runs py.test for a particular project.""" - if os.path.exists("requirements.txt"): - if os.path.exists("constraints.txt"): - session.install("-r", "requirements.txt", "-c", "constraints.txt") - else: - session.install("-r", "requirements.txt") - - if os.path.exists("requirements-test.txt"): - if os.path.exists("constraints-test.txt"): - session.install( - "-r", "requirements-test.txt", "-c", "constraints-test.txt" - ) - else: - session.install("-r", "requirements-test.txt") - - if INSTALL_LIBRARY_FROM_SOURCE: - session.install("-e", _get_repo_root()) - - if post_install: - post_install(session) - - session.run( - "pytest", - *(PYTEST_COMMON_ARGS + session.posargs), - # Pytest will return 5 when no tests are collected. This can happen - # on travis where slow and flaky tests are excluded. - # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html - success_codes=[0, 5], - env=get_pytest_env_vars(), - ) + return + + if TEST_CONFIG["pip_version_override"]: + pip_version = TEST_CONFIG["pip_version_override"] + session.install(f"pip=={pip_version}") + """Runs py.test for a particular project.""" + concurrent_args = [] + if os.path.exists("requirements.txt"): + if os.path.exists("constraints.txt"): + session.install("-r", "requirements.txt", "-c", "constraints.txt") + else: + session.install("-r", "requirements.txt") + with open("requirements.txt") as rfile: + packages = rfile.read() + + if os.path.exists("requirements-test.txt"): + if os.path.exists("constraints-test.txt"): + session.install("-r", "requirements-test.txt", "-c", "constraints-test.txt") + else: + session.install("-r", "requirements-test.txt") + with open("requirements-test.txt") as rtfile: + packages += rtfile.read() + + if INSTALL_LIBRARY_FROM_SOURCE: + session.install("-e", _get_repo_root()) + + if post_install: + post_install(session) + + if "pytest-parallel" in packages: + concurrent_args.extend(["--workers", "auto", "--tests-per-worker", "auto"]) + elif "pytest-xdist" in packages: + concurrent_args.extend(["-n", "auto"]) + + session.run( + "pytest", + *(PYTEST_COMMON_ARGS + session.posargs + concurrent_args), + # Pytest will return 5 when no tests are collected. This can happen + # on travis where slow and flaky tests are excluded. + # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html + success_codes=[0, 5], + env=get_pytest_env_vars(), + ) @nox.session(python=ALL_VERSIONS) From cdbd9c446343ec047b7102ba8c3403781ee6e4bd Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Tue, 8 Mar 2022 02:34:49 +0100 Subject: [PATCH 065/113] chore(samples): Snippet Generating Script: part 2 - moving the snippets to new format (#225) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(samples): Snippet Generating System Preparing the SGS (Snippet Generating System) script to handle the burden of duplicated code in the samples. * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore(samples): Making linter ignore the test fixtures. * chore(samples): Update region tags that shouldn't be region tags. * chore(samples): Syntax fix for Python 3.6 * Removing the 3.8 walrus syntax * Fixing test requirements * chore(samples): Trying to make the tests work * Cleaning up noxfile. * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Removing bonus requirements file as it confuses the system. * One more try to fix tests. * Changing README to make snippet bot happy * chore(samples): Changing the sampels structure to use SGS * Fixing some tests. * Adding custom hostname files. * Making tests work again. * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Making lint happy. * Making tests run faster with parallelism. * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * WIP. * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Apply suggestions from code review Co-authored-by: Anthonios Partheniou * Applying comments from review. Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- compute/compute/__init__.py | 13 + compute/compute/ingredients/__init__.py | 18 + .../ingredients/disks/disk_from_snapshot.py | 54 +++ .../compute/ingredients/disks/empty_disk.py | 49 ++ .../compute/ingredients/disks/from_image.py | 55 +++ .../compute/ingredients/firewall/create.py | 72 +++ .../compute/ingredients/firewall/delete.py | 39 ++ compute/compute/ingredients/firewall/get.py | 36 ++ compute/compute/ingredients/firewall/list.py | 44 ++ compute/compute/ingredients/firewall/patch.py | 46 ++ .../images/get_image.py} | 36 +- .../images/get_image_from_family.py | 42 ++ .../compute/ingredients/images/list_images.py | 37 ++ .../ingredients/instance-templates/create.py | 74 +++ .../create_from_instance.py | 64 +++ .../instance-templates/create_with_subnet.py | 73 +++ .../ingredients/instance-templates/delete.py | 39 ++ .../ingredients/instance-templates/get.py | 40 ++ .../ingredients/instance-templates/list.py | 37 ++ .../compute/ingredients/instances/__init__.py | 18 + .../ingredients/instances/create_instance.py | 124 +++++ .../create_instance_from_template.py | 57 +++ ...e_instance_from_template_with_overrides.py | 97 ++++ .../create_from_custom_image.py | 61 +++ .../create_from_public_image.py | 42 ++ .../create_from_snapshot.py | 43 ++ .../create_with_additional_disk.py | 46 ++ .../create_with_snapshotted_data_disk.py | 48 ++ .../instances/create_with_subnet.py | 57 +++ .../instances/custom_hostname/create.py | 43 ++ .../instances/custom_hostname/get.py | 40 ++ .../create_extra_mem_no_helper.py | 71 +++ .../create_shared_with_helper.py | 60 +++ .../create_with_helper.py | 61 +++ .../create_without_helper.py | 60 +++ .../custom_machine_types/helper_class.py | 211 +++++++++ .../custom_machine_types/update_memory.py | 89 ++++ .../compute/ingredients/instances/delete.py | 56 +++ .../instances/delete_protection/__init__.py | 19 + .../instances/delete_protection/create.py | 44 ++ .../instances/delete_protection/get.py | 38 ++ .../instances/delete_protection/set.py | 47 ++ compute/compute/ingredients/instances/list.py | 44 ++ .../compute/ingredients/instances/list_all.py | 58 +++ .../instances/preemptible/__init__.py | 18 + .../instances/preemptible/create.py | 60 +++ .../ingredients/instances/preemptible/get.py | 38 ++ .../preemptible/preemption_history.py | 56 +++ .../compute/ingredients/instances/reset.py | 46 ++ .../compute/ingredients/instances/start.py | 46 ++ .../ingredients/instances/start_encrypted.py | 68 +++ compute/compute/ingredients/instances/stop.py | 46 ++ .../ingredients/operations/__init__.py | 18 + .../operations/list_zone_operations.py | 46 ++ .../operations/wait_for_operation.py | 50 +++ .../ingredients/usage_report/disable.py | 43 ++ .../ingredients/usage_report/get_bucket.py | 55 +++ .../ingredients/usage_report/set_bucket.py | 61 +++ compute/compute/recipes/__init__.py | 0 compute/compute/recipes/firewall/__init__.py | 0 compute/compute/recipes/firewall/create.py | 21 + compute/compute/recipes/firewall/delete.py | 21 + compute/compute/recipes/firewall/list.py | 21 + compute/compute/recipes/firewall/main.py | 58 +++ compute/compute/recipes/firewall/patch.py | 20 + compute/compute/recipes/images/__init__.py | 0 compute/compute/recipes/images/get.py | 28 ++ compute/compute/recipes/images/list.py | 20 + compute/compute/recipes/images/pagination.py | 96 ++++ .../recipes/instance_templates/__init__.py | 0 .../recipes/instance_templates/create.py | 20 + .../create_from_instance.py | 20 + .../instance_templates/create_with_subnet.py | 20 + .../recipes/instance_templates/delete.py | 20 + .../compute/recipes/instance_templates/get.py | 20 + .../recipes/instance_templates/list.py | 20 + compute/compute/recipes/instances/__init__.py | 0 compute/compute/recipes/instances/create.py | 48 ++ .../create_start_instance/__init__.py | 0 .../create_from_custom_image.py | 29 ++ .../create_from_public_image.py | 29 ++ .../create_from_snapshot.py | 26 ++ .../create_with_additional_disk.py | 33 ++ .../create_with_snapshotted_data_disk.py | 33 ++ .../recipes/instances/create_with_subnet.py | 29 ++ .../instances/custom_hostname/create.py | 29 ++ .../recipes/instances/custom_hostname/get.py | 20 + .../custom_machine_types/__init__.py | 0 .../create_shared_with_helper.py | 32 ++ .../create_with_helper.py | 34 ++ .../create_without_helper.py | 29 ++ .../extra_mem_no_helper.py | 29 ++ .../custom_machine_types/helper_class.py | 20 + .../custom_machine_types/update_memory.py | 20 + compute/compute/recipes/instances/delete.py | 21 + .../instances/delete_protection/__init__.py | 14 + .../instances/delete_protection/create.py | 29 ++ .../instances/delete_protection/get.py | 20 + .../instances/delete_protection/set.py | 20 + .../from_instance_template/__init__.py | 0 .../create_from_template.py | 20 + .../create_from_template_with_overrides.py | 20 + compute/compute/recipes/instances/list.py | 20 + compute/compute/recipes/instances/list_all.py | 20 + .../recipes/instances/preemptible/__init__.py | 14 + .../preemptible/create_preemptible.py | 29 ++ .../instances/preemptible/is_preemptible.py | 21 + .../preemptible/preemption_history.py | 24 + compute/compute/recipes/instances/reset.py | 21 + compute/compute/recipes/instances/start.py | 21 + .../recipes/instances/start_encrypted.py | 21 + compute/compute/recipes/instances/stop.py | 21 + .../compute/recipes/operations/__init__.py | 0 .../recipes/operations/operation_check.py | 33 ++ .../compute/recipes/usage_report/__init__.py | 0 .../recipes/usage_report/usage_reports.py | 45 ++ compute/compute/requirements-test.txt | 1 + compute/compute/sgs.py | 25 +- compute/compute/snippets/__init__.py | 0 compute/compute/snippets/firewall/__init__.py | 0 compute/compute/snippets/firewall/create.py | 74 +++ compute/compute/snippets/firewall/delete.py | 44 ++ compute/compute/snippets/firewall/list.py | 48 ++ .../{sample_firewall.py => firewall/main.py} | 133 +++--- compute/compute/snippets/firewall/patch.py | 50 +++ compute/compute/snippets/images/__init__.py | 0 compute/compute/snippets/images/get.py | 55 +++ compute/compute/snippets/images/list.py | 42 ++ .../pagination.py} | 21 +- .../snippets/instance_templates/__init__.py | 0 .../snippets/instance_templates/create.py | 78 ++++ .../create_from_instance.py | 68 +++ .../instance_templates/create_with_subnet.py | 77 ++++ .../snippets/instance_templates/delete.py | 43 ++ .../snippets/instance_templates/get.py | 44 ++ .../snippets/instance_templates/list.py | 42 ++ .../compute/snippets/instances/__init__.py | 0 compute/compute/snippets/instances/create.py | 195 ++++++++ .../create_start_instance/__init__.py | 0 .../create_from_custom_image.py | 193 ++++++++ .../create_from_public_image.py | 192 ++++++++ .../create_from_snapshot.py | 185 ++++++++ .../create_with_additional_disk.py | 222 +++++++++ .../create_with_snapshotted_data_disk.py | 229 ++++++++++ .../snippets/instances/create_with_subnet.py | 205 +++++++++ .../instances/custom_hostname/create.py | 195 ++++++++ .../snippets/instances/custom_hostname/get.py | 45 ++ .../custom_machine_types/__init__.py | 0 .../create_shared_with_helper.py | 390 ++++++++++++++++ .../create_with_helper.py} | 364 +++++---------- .../create_without_helper.py | 245 ++++++++++ .../extra_mem_no_helper.py | 217 +++++++++ .../custom_machine_types/helper_class.py | 209 +++++++++ .../custom_machine_types/update_memory.py | 87 ++++ compute/compute/snippets/instances/delete.py | 56 +++ .../instances/delete_protection/__init__.py | 14 + .../instances/delete_protection/create.py | 195 ++++++++ .../instances/delete_protection/get.py | 43 ++ .../instances/delete_protection/set.py | 52 +++ .../from_instance_template/__init__.py | 0 .../create_from_template.py | 61 +++ .../create_from_template_with_overrides.py} | 51 +-- compute/compute/snippets/instances/list.py | 48 ++ .../compute/snippets/instances/list_all.py | 62 +++ .../instances/preemptible/__init__.py | 14 + .../preemptible/create_preemptible.py | 192 ++++++++ .../instances/preemptible/is_preemptible.py | 43 ++ .../preemptible/preemption_history.py | 87 ++++ compute/compute/snippets/instances/reset.py | 46 ++ compute/compute/snippets/instances/start.py | 46 ++ .../snippets/instances/start_encrypted.py | 68 +++ compute/compute/snippets/instances/stop.py | 46 ++ .../compute/snippets/operations/__init__.py | 0 .../snippets/operations/operation_check.py | 68 +++ compute/compute/snippets/quickstart.py | 278 ------------ compute/compute/snippets/sample_create_vm.py | 423 ------------------ .../snippets/sample_custom_hostname.py | 118 ----- .../snippets/sample_delete_protection.py | 147 ------ .../compute/snippets/sample_preemptible.py | 193 -------- compute/compute/snippets/sample_start_stop.py | 152 ------- compute/compute/snippets/sample_templates.py | 251 ----------- compute/compute/snippets/test_quickstart.py | 36 -- compute/compute/snippets/tests/__init__.py | 13 + .../test_custom_hostnames.py} | 12 +- .../test_delete_protection.py} | 12 +- .../test_preemptible.py} | 8 +- .../{ => tests}/test_sample_create_vm.py | 76 ++-- .../{ => tests}/test_sample_custom_types.py | 30 +- .../{ => tests}/test_sample_default_values.py | 8 +- .../{ => tests}/test_sample_firewall.py | 11 +- .../{ => tests}/test_sample_images.py | 3 +- .../test_sample_instance_from_template.py | 7 +- .../{ => tests}/test_sample_pagination.py | 15 +- .../{ => tests}/test_sample_start_stop.py | 9 +- .../{ => tests}/test_sample_templates.py | 17 +- .../compute/snippets/usage_report/__init__.py | 0 .../usage_reports.py} | 37 +- compute/compute/test_sgs.py | 4 +- 198 files changed, 9278 insertions(+), 2124 deletions(-) create mode 100644 compute/compute/__init__.py create mode 100644 compute/compute/ingredients/__init__.py create mode 100644 compute/compute/ingredients/disks/disk_from_snapshot.py create mode 100644 compute/compute/ingredients/disks/empty_disk.py create mode 100644 compute/compute/ingredients/disks/from_image.py create mode 100644 compute/compute/ingredients/firewall/create.py create mode 100644 compute/compute/ingredients/firewall/delete.py create mode 100644 compute/compute/ingredients/firewall/get.py create mode 100644 compute/compute/ingredients/firewall/list.py create mode 100644 compute/compute/ingredients/firewall/patch.py rename compute/compute/{snippets/sample_images.py => ingredients/images/get_image.py} (60%) create mode 100644 compute/compute/ingredients/images/get_image_from_family.py create mode 100644 compute/compute/ingredients/images/list_images.py create mode 100644 compute/compute/ingredients/instance-templates/create.py create mode 100644 compute/compute/ingredients/instance-templates/create_from_instance.py create mode 100644 compute/compute/ingredients/instance-templates/create_with_subnet.py create mode 100644 compute/compute/ingredients/instance-templates/delete.py create mode 100644 compute/compute/ingredients/instance-templates/get.py create mode 100644 compute/compute/ingredients/instance-templates/list.py create mode 100644 compute/compute/ingredients/instances/__init__.py create mode 100644 compute/compute/ingredients/instances/create_instance.py create mode 100644 compute/compute/ingredients/instances/create_instance_from_template.py create mode 100644 compute/compute/ingredients/instances/create_instance_from_template_with_overrides.py create mode 100644 compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.py create mode 100644 compute/compute/ingredients/instances/create_start_instance/create_from_public_image.py create mode 100644 compute/compute/ingredients/instances/create_start_instance/create_from_snapshot.py create mode 100644 compute/compute/ingredients/instances/create_start_instance/create_with_additional_disk.py create mode 100644 compute/compute/ingredients/instances/create_start_instance/create_with_snapshotted_data_disk.py create mode 100644 compute/compute/ingredients/instances/create_with_subnet.py create mode 100644 compute/compute/ingredients/instances/custom_hostname/create.py create mode 100644 compute/compute/ingredients/instances/custom_hostname/get.py create mode 100644 compute/compute/ingredients/instances/custom_machine_types/create_extra_mem_no_helper.py create mode 100644 compute/compute/ingredients/instances/custom_machine_types/create_shared_with_helper.py create mode 100644 compute/compute/ingredients/instances/custom_machine_types/create_with_helper.py create mode 100644 compute/compute/ingredients/instances/custom_machine_types/create_without_helper.py create mode 100644 compute/compute/ingredients/instances/custom_machine_types/helper_class.py create mode 100644 compute/compute/ingredients/instances/custom_machine_types/update_memory.py create mode 100644 compute/compute/ingredients/instances/delete.py create mode 100644 compute/compute/ingredients/instances/delete_protection/__init__.py create mode 100644 compute/compute/ingredients/instances/delete_protection/create.py create mode 100644 compute/compute/ingredients/instances/delete_protection/get.py create mode 100644 compute/compute/ingredients/instances/delete_protection/set.py create mode 100644 compute/compute/ingredients/instances/list.py create mode 100644 compute/compute/ingredients/instances/list_all.py create mode 100644 compute/compute/ingredients/instances/preemptible/__init__.py create mode 100644 compute/compute/ingredients/instances/preemptible/create.py create mode 100644 compute/compute/ingredients/instances/preemptible/get.py create mode 100644 compute/compute/ingredients/instances/preemptible/preemption_history.py create mode 100644 compute/compute/ingredients/instances/reset.py create mode 100644 compute/compute/ingredients/instances/start.py create mode 100644 compute/compute/ingredients/instances/start_encrypted.py create mode 100644 compute/compute/ingredients/instances/stop.py create mode 100644 compute/compute/ingredients/operations/__init__.py create mode 100644 compute/compute/ingredients/operations/list_zone_operations.py create mode 100644 compute/compute/ingredients/operations/wait_for_operation.py create mode 100644 compute/compute/ingredients/usage_report/disable.py create mode 100644 compute/compute/ingredients/usage_report/get_bucket.py create mode 100644 compute/compute/ingredients/usage_report/set_bucket.py create mode 100644 compute/compute/recipes/__init__.py create mode 100644 compute/compute/recipes/firewall/__init__.py create mode 100644 compute/compute/recipes/firewall/create.py create mode 100644 compute/compute/recipes/firewall/delete.py create mode 100644 compute/compute/recipes/firewall/list.py create mode 100644 compute/compute/recipes/firewall/main.py create mode 100644 compute/compute/recipes/firewall/patch.py create mode 100644 compute/compute/recipes/images/__init__.py create mode 100644 compute/compute/recipes/images/get.py create mode 100644 compute/compute/recipes/images/list.py create mode 100644 compute/compute/recipes/images/pagination.py create mode 100644 compute/compute/recipes/instance_templates/__init__.py create mode 100644 compute/compute/recipes/instance_templates/create.py create mode 100644 compute/compute/recipes/instance_templates/create_from_instance.py create mode 100644 compute/compute/recipes/instance_templates/create_with_subnet.py create mode 100644 compute/compute/recipes/instance_templates/delete.py create mode 100644 compute/compute/recipes/instance_templates/get.py create mode 100644 compute/compute/recipes/instance_templates/list.py create mode 100644 compute/compute/recipes/instances/__init__.py create mode 100644 compute/compute/recipes/instances/create.py create mode 100644 compute/compute/recipes/instances/create_start_instance/__init__.py create mode 100644 compute/compute/recipes/instances/create_start_instance/create_from_custom_image.py create mode 100644 compute/compute/recipes/instances/create_start_instance/create_from_public_image.py create mode 100644 compute/compute/recipes/instances/create_start_instance/create_from_snapshot.py create mode 100644 compute/compute/recipes/instances/create_start_instance/create_with_additional_disk.py create mode 100644 compute/compute/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.py create mode 100644 compute/compute/recipes/instances/create_with_subnet.py create mode 100644 compute/compute/recipes/instances/custom_hostname/create.py create mode 100644 compute/compute/recipes/instances/custom_hostname/get.py create mode 100644 compute/compute/recipes/instances/custom_machine_types/__init__.py create mode 100644 compute/compute/recipes/instances/custom_machine_types/create_shared_with_helper.py create mode 100644 compute/compute/recipes/instances/custom_machine_types/create_with_helper.py create mode 100644 compute/compute/recipes/instances/custom_machine_types/create_without_helper.py create mode 100644 compute/compute/recipes/instances/custom_machine_types/extra_mem_no_helper.py create mode 100644 compute/compute/recipes/instances/custom_machine_types/helper_class.py create mode 100644 compute/compute/recipes/instances/custom_machine_types/update_memory.py create mode 100644 compute/compute/recipes/instances/delete.py create mode 100644 compute/compute/recipes/instances/delete_protection/__init__.py create mode 100644 compute/compute/recipes/instances/delete_protection/create.py create mode 100644 compute/compute/recipes/instances/delete_protection/get.py create mode 100644 compute/compute/recipes/instances/delete_protection/set.py create mode 100644 compute/compute/recipes/instances/from_instance_template/__init__.py create mode 100644 compute/compute/recipes/instances/from_instance_template/create_from_template.py create mode 100644 compute/compute/recipes/instances/from_instance_template/create_from_template_with_overrides.py create mode 100644 compute/compute/recipes/instances/list.py create mode 100644 compute/compute/recipes/instances/list_all.py create mode 100644 compute/compute/recipes/instances/preemptible/__init__.py create mode 100644 compute/compute/recipes/instances/preemptible/create_preemptible.py create mode 100644 compute/compute/recipes/instances/preemptible/is_preemptible.py create mode 100644 compute/compute/recipes/instances/preemptible/preemption_history.py create mode 100644 compute/compute/recipes/instances/reset.py create mode 100644 compute/compute/recipes/instances/start.py create mode 100644 compute/compute/recipes/instances/start_encrypted.py create mode 100644 compute/compute/recipes/instances/stop.py create mode 100644 compute/compute/recipes/operations/__init__.py create mode 100644 compute/compute/recipes/operations/operation_check.py create mode 100644 compute/compute/recipes/usage_report/__init__.py create mode 100644 compute/compute/recipes/usage_report/usage_reports.py create mode 100644 compute/compute/snippets/__init__.py create mode 100644 compute/compute/snippets/firewall/__init__.py create mode 100644 compute/compute/snippets/firewall/create.py create mode 100644 compute/compute/snippets/firewall/delete.py create mode 100644 compute/compute/snippets/firewall/list.py rename compute/compute/snippets/{sample_firewall.py => firewall/main.py} (82%) create mode 100644 compute/compute/snippets/firewall/patch.py create mode 100644 compute/compute/snippets/images/__init__.py create mode 100644 compute/compute/snippets/images/get.py create mode 100644 compute/compute/snippets/images/list.py rename compute/compute/snippets/{sample_pagination.py => images/pagination.py} (85%) create mode 100644 compute/compute/snippets/instance_templates/__init__.py create mode 100644 compute/compute/snippets/instance_templates/create.py create mode 100644 compute/compute/snippets/instance_templates/create_from_instance.py create mode 100644 compute/compute/snippets/instance_templates/create_with_subnet.py create mode 100644 compute/compute/snippets/instance_templates/delete.py create mode 100644 compute/compute/snippets/instance_templates/get.py create mode 100644 compute/compute/snippets/instance_templates/list.py create mode 100644 compute/compute/snippets/instances/__init__.py create mode 100644 compute/compute/snippets/instances/create.py create mode 100644 compute/compute/snippets/instances/create_start_instance/__init__.py create mode 100644 compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py create mode 100644 compute/compute/snippets/instances/create_start_instance/create_from_public_image.py create mode 100644 compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py create mode 100644 compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py create mode 100644 compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py create mode 100644 compute/compute/snippets/instances/create_with_subnet.py create mode 100644 compute/compute/snippets/instances/custom_hostname/create.py create mode 100644 compute/compute/snippets/instances/custom_hostname/get.py create mode 100644 compute/compute/snippets/instances/custom_machine_types/__init__.py create mode 100644 compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py rename compute/compute/snippets/{sample_custom_types.py => instances/custom_machine_types/create_with_helper.py} (54%) create mode 100644 compute/compute/snippets/instances/custom_machine_types/create_without_helper.py create mode 100644 compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py create mode 100644 compute/compute/snippets/instances/custom_machine_types/helper_class.py create mode 100644 compute/compute/snippets/instances/custom_machine_types/update_memory.py create mode 100644 compute/compute/snippets/instances/delete.py create mode 100644 compute/compute/snippets/instances/delete_protection/__init__.py create mode 100644 compute/compute/snippets/instances/delete_protection/create.py create mode 100644 compute/compute/snippets/instances/delete_protection/get.py create mode 100644 compute/compute/snippets/instances/delete_protection/set.py create mode 100644 compute/compute/snippets/instances/from_instance_template/__init__.py create mode 100644 compute/compute/snippets/instances/from_instance_template/create_from_template.py rename compute/compute/snippets/{sample_instance_from_template.py => instances/from_instance_template/create_from_template_with_overrides.py} (67%) create mode 100644 compute/compute/snippets/instances/list.py create mode 100644 compute/compute/snippets/instances/list_all.py create mode 100644 compute/compute/snippets/instances/preemptible/__init__.py create mode 100644 compute/compute/snippets/instances/preemptible/create_preemptible.py create mode 100644 compute/compute/snippets/instances/preemptible/is_preemptible.py create mode 100644 compute/compute/snippets/instances/preemptible/preemption_history.py create mode 100644 compute/compute/snippets/instances/reset.py create mode 100644 compute/compute/snippets/instances/start.py create mode 100644 compute/compute/snippets/instances/start_encrypted.py create mode 100644 compute/compute/snippets/instances/stop.py create mode 100644 compute/compute/snippets/operations/__init__.py create mode 100644 compute/compute/snippets/operations/operation_check.py delete mode 100644 compute/compute/snippets/quickstart.py delete mode 100644 compute/compute/snippets/sample_create_vm.py delete mode 100644 compute/compute/snippets/sample_custom_hostname.py delete mode 100644 compute/compute/snippets/sample_delete_protection.py delete mode 100644 compute/compute/snippets/sample_preemptible.py delete mode 100644 compute/compute/snippets/sample_start_stop.py delete mode 100644 compute/compute/snippets/sample_templates.py delete mode 100644 compute/compute/snippets/test_quickstart.py create mode 100644 compute/compute/snippets/tests/__init__.py rename compute/compute/snippets/{test_sample_custom_hostname.py => tests/test_custom_hostnames.py} (77%) rename compute/compute/snippets/{test_sample_delete_protection.py => tests/test_delete_protection.py} (80%) rename compute/compute/snippets/{test_sample_preemptible.py => tests/test_preemptible.py} (85%) rename compute/compute/snippets/{ => tests}/test_sample_create_vm.py (72%) rename compute/compute/snippets/{ => tests}/test_sample_custom_types.py (84%) rename compute/compute/snippets/{ => tests}/test_sample_default_values.py (93%) rename compute/compute/snippets/{ => tests}/test_sample_firewall.py (94%) rename compute/compute/snippets/{ => tests}/test_sample_images.py (93%) rename compute/compute/snippets/{ => tests}/test_sample_instance_from_template.py (94%) rename compute/compute/snippets/{ => tests}/test_sample_pagination.py (66%) rename compute/compute/snippets/{ => tests}/test_sample_start_stop.py (97%) rename compute/compute/snippets/{ => tests}/test_sample_templates.py (88%) create mode 100644 compute/compute/snippets/usage_report/__init__.py rename compute/compute/snippets/{sample_default_values.py => usage_report/usage_reports.py} (81%) diff --git a/compute/compute/__init__.py b/compute/compute/__init__.py new file mode 100644 index 00000000000..4bbe0ffdb06 --- /dev/null +++ b/compute/compute/__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/compute/ingredients/__init__.py b/compute/compute/ingredients/__init__.py new file mode 100644 index 00000000000..81d8b9be3da --- /dev/null +++ b/compute/compute/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/compute/ingredients/disks/disk_from_snapshot.py b/compute/compute/ingredients/disks/disk_from_snapshot.py new file mode 100644 index 00000000000..e0271c47bc4 --- /dev/null +++ b/compute/compute/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 = False +) -> 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/compute/ingredients/disks/empty_disk.py b/compute/compute/ingredients/disks/empty_disk.py new file mode 100644 index 00000000000..570292fde9e --- /dev/null +++ b/compute/compute/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 = False) -> 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 = True + disk.boot = False + return disk +# diff --git a/compute/compute/ingredients/disks/from_image.py b/compute/compute/ingredients/disks/from_image.py new file mode 100644 index 00000000000..5f22f4e6176 --- /dev/null +++ b/compute/compute/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 = False +) -> 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/compute/ingredients/firewall/create.py b/compute/compute/ingredients/firewall/create.py new file mode 100644 index 00000000000..e6e9f000818 --- /dev/null +++ b/compute/compute/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() + op = firewall_client.insert_unary( + project=project_id, firewall_resource=firewall_rule + ) + + op_client = compute_v1.GlobalOperationsClient() + op_client.wait(project=project_id, operation=op.name) + + return firewall_client.get(project=project_id, firewall=firewall_rule_name) +# diff --git a/compute/compute/ingredients/firewall/delete.py b/compute/compute/ingredients/firewall/delete.py new file mode 100644 index 00000000000..fc6a42150fa --- /dev/null +++ b/compute/compute/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_unary( + project=project_id, firewall=firewall_rule_name + ) + + operation_client = compute_v1.GlobalOperationsClient() + operation_client.wait(project=project_id, operation=operation.name) + return +# diff --git a/compute/compute/ingredients/firewall/get.py b/compute/compute/ingredients/firewall/get.py new file mode 100644 index 00000000000..0a8388d5699 --- /dev/null +++ b/compute/compute/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/compute/ingredients/firewall/list.py b/compute/compute/ingredients/firewall/list.py new file mode 100644 index 00000000000..5deeac4e3b7 --- /dev/null +++ b/compute/compute/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/compute/ingredients/firewall/patch.py b/compute/compute/ingredients/firewall/patch.py new file mode 100644 index 00000000000..5017114a33d --- /dev/null +++ b/compute/compute/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_unary( + project=project_id, firewall=firewall_rule_name, firewall_resource=firewall_rule + ) + + operation_client = compute_v1.GlobalOperationsClient() + operation_client.wait(project=project_id, operation=operation.name) + return +# + diff --git a/compute/compute/snippets/sample_images.py b/compute/compute/ingredients/images/get_image.py similarity index 60% rename from compute/compute/snippets/sample_images.py rename to compute/compute/ingredients/images/get_image.py index 96d8bbb8302..4dcce0e77c0 100644 --- a/compute/compute/snippets/sample_images.py +++ b/compute/compute/ingredients/images/get_image.py @@ -1,4 +1,4 @@ -# Copyright 2021 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,35 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Iterable +# 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 -# [START compute_images_get] -# [START compute_images_get_list] from google.cloud import compute_v1 -# [END compute_images_get_list] -# [END compute_images_get] - -# [START compute_images_get_list] -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] - - -# [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. @@ -54,6 +34,4 @@ def get_image(project_id: str, image_name: str) -> compute_v1.Image: """ image_client = compute_v1.ImagesClient() return image_client.get(project=project_id, image=image_name) - - -# [END compute_images_get] +# diff --git a/compute/compute/ingredients/images/get_image_from_family.py b/compute/compute/ingredients/images/get_image_from_family.py new file mode 100644 index 00000000000..45daec115fe --- /dev/null +++ b/compute/compute/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/compute/ingredients/images/list_images.py b/compute/compute/ingredients/images/list_images.py new file mode 100644 index 00000000000..b4c191fc3e2 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instance-templates/create.py b/compute/compute/ingredients/instance-templates/create.py new file mode 100644 index 00000000000..ca56e99a84f --- /dev/null +++ b/compute/compute/ingredients/instance-templates/create.py @@ -0,0 +1,74 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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(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_client = compute_v1.GlobalOperationsClient() + op = template_client.insert_unary( + project=project_id, instance_template_resource=template + ) + operation_client.wait(project=project_id, operation=op.name) + + return template_client.get(project=project_id, instance_template=template_name) +# diff --git a/compute/compute/ingredients/instance-templates/create_from_instance.py b/compute/compute/ingredients/instance-templates/create_from_instance.py new file mode 100644 index 00000000000..584e2f17724 --- /dev/null +++ b/compute/compute/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_client = compute_v1.GlobalOperationsClient() + op = template_client.insert_unary( + project=project_id, instance_template_resource=template + ) + operation_client.wait(project=project_id, operation=op.name) + + return template_client.get(project=project_id, instance_template=template_name) +# diff --git a/compute/compute/ingredients/instance-templates/create_with_subnet.py b/compute/compute/ingredients/instance-templates/create_with_subnet.py new file mode 100644 index 00000000000..fb80d2510a2 --- /dev/null +++ b/compute/compute/ingredients/instance-templates/create_with_subnet.py @@ -0,0 +1,73 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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_client = compute_v1.GlobalOperationsClient() + op = template_client.insert_unary( + project=project_id, instance_template_resource=template + ) + operation_client.wait(project=project_id, operation=op.name) + + return template_client.get(project=project_id, instance_template=template_name) +# diff --git a/compute/compute/ingredients/instance-templates/delete.py b/compute/compute/ingredients/instance-templates/delete.py new file mode 100644 index 00000000000..23bd929eeec --- /dev/null +++ b/compute/compute/ingredients/instance-templates/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_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_client = compute_v1.GlobalOperationsClient() + op = template_client.delete_unary( + project=project_id, instance_template=template_name + ) + operation_client.wait(project=project_id, operation=op.name) + return +# diff --git a/compute/compute/ingredients/instance-templates/get.py b/compute/compute/ingredients/instance-templates/get.py new file mode 100644 index 00000000000..99aae684df0 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instance-templates/list.py b/compute/compute/ingredients/instance-templates/list.py new file mode 100644 index 00000000000..851e2c48e50 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/__init__.py b/compute/compute/ingredients/instances/__init__.py new file mode 100644 index 00000000000..81d8b9be3da --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/create_instance.py b/compute/compute/ingredients/instances/create_instance.py new file mode 100644 index 00000000000..85c2e48185e --- /dev/null +++ b/compute/compute/ingredients/instances/create_instance.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. + +# 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 +import sys +from google.cloud import compute_v1 +import time +from typing import List + + +# +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, + preemptible: bool = False, + 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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_unary(request=request) + start = time.time() + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance +# diff --git a/compute/compute/ingredients/instances/create_instance_from_template.py b/compute/compute/ingredients/instances/create_instance_from_template.py new file mode 100644 index 00000000000..73ff814f4bb --- /dev/null +++ b/compute/compute/ingredients/instances/create_instance_from_template.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_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. + """ + operation_client = compute_v1.ZoneOperationsClient() + 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 + + op = instance_client.insert_unary(instance_insert_request) + operation_client.wait(project=project_id, zone=zone, operation=op.name) + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) +# diff --git a/compute/compute/ingredients/instances/create_instance_from_template_with_overrides.py b/compute/compute/ingredients/instances/create_instance_from_template_with_overrides.py new file mode 100644 index 00000000000..001cb5179b5 --- /dev/null +++ b/compute/compute/ingredients/instances/create_instance_from_template_with_overrides.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. + +# 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. + """ + operation_client = compute_v1.ZoneOperationsClient() + 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 = 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 + + op = instance_client.insert_unary(instance_insert_request) + operation_client.wait(project=project_id, zone=zone, operation=op.name) + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) +# \ No newline at end of file diff --git a/compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.py b/compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.py new file mode 100644 index 00000000000..2d297cbca98 --- /dev/null +++ b/compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.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 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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)] + instance = create_instance(project_id, zone, instance_name, disks) + return instance +# diff --git a/compute/compute/ingredients/instances/create_start_instance/create_from_public_image.py b/compute/compute/ingredients/instances/create_start_instance/create_from_public_image.py new file mode 100644 index 00000000000..2eb8e3c2e15 --- /dev/null +++ b/compute/compute/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)] + instance = create_instance(project_id, zone, instance_name, disks) + return instance +# diff --git a/compute/compute/ingredients/instances/create_start_instance/create_from_snapshot.py b/compute/compute/ingredients/instances/create_start_instance/create_from_snapshot.py new file mode 100644 index 00000000000..a2729332cf1 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/create_start_instance/create_with_additional_disk.py b/compute/compute/ingredients/instances/create_start_instance/create_with_additional_disk.py new file mode 100644 index 00000000000..b921f27a3a0 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/create_start_instance/create_with_snapshotted_data_disk.py b/compute/compute/ingredients/instances/create_start_instance/create_with_snapshotted_data_disk.py new file mode 100644 index 00000000000..ea87201e5f1 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/create_with_subnet.py b/compute/compute/ingredients/instances/create_with_subnet.py new file mode 100644 index 00000000000..bc39ff2231a --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/custom_hostname/create.py b/compute/compute/ingredients/instances/custom_hostname/create.py new file mode 100644 index 00000000000..9a990cb9fa4 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/custom_hostname/get.py b/compute/compute/ingredients/instances/custom_hostname/get.py new file mode 100644 index 00000000000..b362fce26e1 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/custom_machine_types/create_extra_mem_no_helper.py b/compute/compute/ingredients/instances/custom_machine_types/create_extra_mem_no_helper.py new file mode 100644 index 00000000000..536455f669d --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/custom_machine_types/create_shared_with_helper.py b/compute/compute/ingredients/instances/custom_machine_types/create_shared_with_helper.py new file mode 100644 index 00000000000..a29193438ff --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/custom_machine_types/create_with_helper.py b/compute/compute/ingredients/instances/custom_machine_types/create_with_helper.py new file mode 100644 index 00000000000..2731f40da89 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/custom_machine_types/create_without_helper.py b/compute/compute/ingredients/instances/custom_machine_types/create_without_helper.py new file mode 100644 index 00000000000..a17a979bb8f --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/custom_machine_types/helper_class.py b/compute/compute/ingredients/instances/custom_machine_types/helper_class.py new file mode 100644 index 00000000000..616961943b8 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/custom_machine_types/update_memory.py b/compute/compute/ingredients/instances/custom_machine_types/update_memory.py new file mode 100644 index 00000000000..ee9ed2a75f2 --- /dev/null +++ b/compute/compute/ingredients/instances/custom_machine_types/update_memory.py @@ -0,0 +1,89 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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() + operation_client = compute_v1.ZoneOperationsClient() + 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, + ): + op = instance_client.stop_unary( + project=project_id, zone=zone, instance=instance_name + ) + operation_client.wait(project=project_id, zone=zone, operation=op.name) + 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) + op = instance_client.update_unary( + project=project_id, + zone=zone, + instance=instance_name, + instance_resource=instance, + ) + operation_client.wait(project=project_id, zone=zone, operation=op.name) + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) +# diff --git a/compute/compute/ingredients/instances/delete.py b/compute/compute/ingredients/instances/delete.py new file mode 100644 index 00000000000..ee84a349c34 --- /dev/null +++ b/compute/compute/ingredients/instances/delete.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 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() + operation_client = compute_v1.ZoneOperationsClient() + + print(f"Deleting {machine_name} from {zone}...") + operation = instance_client.delete_unary( + project=project_id, zone=zone, instance=machine_name + ) + start = time.time() + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() + if operation.error: + print("Error during deletion:", operation.error, file=sys.stderr) + return + if operation.warnings: + print("Warning during deletion:", operation.warnings, file=sys.stderr) + print(f"Instance {machine_name} deleted.") + return +# diff --git a/compute/compute/ingredients/instances/delete_protection/__init__.py b/compute/compute/ingredients/instances/delete_protection/__init__.py new file mode 100644 index 00000000000..8fb7cb024dd --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/delete_protection/create.py b/compute/compute/ingredients/instances/delete_protection/create.py new file mode 100644 index 00000000000..eb431ec7e84 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/delete_protection/get.py b/compute/compute/ingredients/instances/delete_protection/get.py new file mode 100644 index 00000000000..f57b1624e05 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/delete_protection/set.py b/compute/compute/ingredients/instances/delete_protection/set.py new file mode 100644 index 00000000000..fd7bd4ca919 --- /dev/null +++ b/compute/compute/ingredients/instances/delete_protection/set.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 +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() + operation_client = compute_v1.ZoneOperationsClient() + + 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_unary(request) + operation_client.wait(project=project_id, zone=zone, operation=operation.name) + return +# diff --git a/compute/compute/ingredients/instances/list.py b/compute/compute/ingredients/instances/list.py new file mode 100644 index 00000000000..089f7fdabb8 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/list_all.py b/compute/compute/ingredients/instances/list_all.py new file mode 100644 index 00000000000..ced8e7a1f27 --- /dev/null +++ b/compute/compute/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 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 = {} + 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] = response.instances + print(f" {zone}:") + for instance in response.instances: + print(f" - {instance.name} ({instance.machine_type})") + return all_instances +# + diff --git a/compute/compute/ingredients/instances/preemptible/__init__.py b/compute/compute/ingredients/instances/preemptible/__init__.py new file mode 100644 index 00000000000..81d8b9be3da --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/preemptible/create.py b/compute/compute/ingredients/instances/preemptible/create.py new file mode 100644 index 00000000000..46d611a5dca --- /dev/null +++ b/compute/compute/ingredients/instances/preemptible/create.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 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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/compute/ingredients/instances/preemptible/get.py b/compute/compute/ingredients/instances/preemptible/get.py new file mode 100644 index 00000000000..7ff9fc860e6 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/preemptible/preemption_history.py b/compute/compute/ingredients/instances/preemptible/preemption_history.py new file mode 100644 index 00000000000..53b6a3da767 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/reset.py b/compute/compute/ingredients/instances/reset.py new file mode 100644 index 00000000000..a0d29a1d9a3 --- /dev/null +++ b/compute/compute/ingredients/instances/reset.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 +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() + op_client = compute_v1.ZoneOperationsClient() + + op = instance_client.reset_unary( + project=project_id, zone=zone, instance=instance_name + ) + + start = time.time() + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait(operation=op.name, zone=zone, project=project_id) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() + return +# diff --git a/compute/compute/ingredients/instances/start.py b/compute/compute/ingredients/instances/start.py new file mode 100644 index 00000000000..a57359b103b --- /dev/null +++ b/compute/compute/ingredients/instances/start.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 +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() + op_client = compute_v1.ZoneOperationsClient() + + op = instance_client.start_unary( + project=project_id, zone=zone, instance=instance_name + ) + + start = time.time() + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait(operation=op.name, zone=zone, project=project_id) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() + return +# diff --git a/compute/compute/ingredients/instances/start_encrypted.py b/compute/compute/ingredients/instances/start_encrypted.py new file mode 100644 index 00000000000..e90c56f2a8b --- /dev/null +++ b/compute/compute/ingredients/instances/start_encrypted.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 +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() + op_client = compute_v1.ZoneOperationsClient() + + 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] + + op = instance_client.start_with_encryption_key_unary( + project=project_id, + zone=zone, + instance=instance_name, + instances_start_with_encryption_key_request_resource=enc_data, + ) + + start = time.time() + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait(operation=op.name, zone=zone, project=project_id) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() + return +# diff --git a/compute/compute/ingredients/instances/stop.py b/compute/compute/ingredients/instances/stop.py new file mode 100644 index 00000000000..903053348bc --- /dev/null +++ b/compute/compute/ingredients/instances/stop.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 +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() + op_client = compute_v1.ZoneOperationsClient() + + op = instance_client.stop_unary( + project=project_id, zone=zone, instance=instance_name + ) + + start = time.time() + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait(operation=op.name, zone=zone, project=project_id) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() + return +# diff --git a/compute/compute/ingredients/operations/__init__.py b/compute/compute/ingredients/operations/__init__.py new file mode 100644 index 00000000000..81d8b9be3da --- /dev/null +++ b/compute/compute/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/compute/ingredients/operations/list_zone_operations.py b/compute/compute/ingredients/operations/list_zone_operations.py new file mode 100644 index 00000000000..7089f023f8d --- /dev/null +++ b/compute/compute/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) +# \ No newline at end of file diff --git a/compute/compute/ingredients/operations/wait_for_operation.py b/compute/compute/ingredients/operations/wait_for_operation.py new file mode 100644 index 00000000000..53913076e69 --- /dev/null +++ b/compute/compute/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/compute/ingredients/usage_report/disable.py b/compute/compute/ingredients/usage_report/disable.py new file mode 100644 index 00000000000..e8d1464cbca --- /dev/null +++ b/compute/compute/ingredients/usage_report/disable.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 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_unary( + project=project_id, usage_export_location_resource={} + ) + + op_client = compute_v1.GlobalOperationsClient() + + while operation.status != compute_v1.Operation.Status.DONE: + operation = op_client.wait(operation=operation.name, project=project_id) +# + diff --git a/compute/compute/ingredients/usage_report/get_bucket.py b/compute/compute/ingredients/usage_report/get_bucket.py new file mode 100644 index 00000000000..8b5a3b0b4e4 --- /dev/null +++ b/compute/compute/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/compute/ingredients/usage_report/set_bucket.py b/compute/compute/ingredients/usage_report/set_bucket.py new file mode 100644 index 00000000000..7a948c8b706 --- /dev/null +++ b/compute/compute/ingredients/usage_report/set_bucket.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 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_unary( + project=project_id, usage_export_location_resource=usage_export_location + ) + + op_client = compute_v1.GlobalOperationsClient() + + while operation.status != compute_v1.Operation.Status.DONE: + operation = op_client.wait(operation=operation.name, project=project_id) +# + diff --git a/compute/compute/recipes/__init__.py b/compute/compute/recipes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/recipes/firewall/__init__.py b/compute/compute/recipes/firewall/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/recipes/firewall/create.py b/compute/compute/recipes/firewall/create.py new file mode 100644 index 00000000000..8d76598fa1d --- /dev/null +++ b/compute/compute/recipes/firewall/create.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/compute/recipes/firewall/delete.py b/compute/compute/recipes/firewall/delete.py new file mode 100644 index 00000000000..6c3752fa643 --- /dev/null +++ b/compute/compute/recipes/firewall/delete.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/compute/recipes/firewall/list.py b/compute/compute/recipes/firewall/list.py new file mode 100644 index 00000000000..fbd0149f667 --- /dev/null +++ b/compute/compute/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/compute/recipes/firewall/main.py b/compute/compute/recipes/firewall/main.py new file mode 100644 index 00000000000..c8a4d83b0d3 --- /dev/null +++ b/compute/compute/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/compute/recipes/firewall/patch.py b/compute/compute/recipes/firewall/patch.py new file mode 100644 index 00000000000..543157e1ac1 --- /dev/null +++ b/compute/compute/recipes/firewall/patch.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/compute/recipes/images/__init__.py b/compute/compute/recipes/images/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/recipes/images/get.py b/compute/compute/recipes/images/get.py new file mode 100644 index 00000000000..4524e8f2f22 --- /dev/null +++ b/compute/compute/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/compute/recipes/images/list.py b/compute/compute/recipes/images/list.py new file mode 100644 index 00000000000..80d3074cdc3 --- /dev/null +++ b/compute/compute/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/compute/recipes/images/pagination.py b/compute/compute/recipes/images/pagination.py new file mode 100644 index 00000000000..aa58b4f86a3 --- /dev/null +++ b/compute/compute/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/compute/recipes/instance_templates/__init__.py b/compute/compute/recipes/instance_templates/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/recipes/instance_templates/create.py b/compute/compute/recipes/instance_templates/create.py new file mode 100644 index 00000000000..6c313c2d28e --- /dev/null +++ b/compute/compute/recipes/instance_templates/create.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/compute/recipes/instance_templates/create_from_instance.py b/compute/compute/recipes/instance_templates/create_from_instance.py new file mode 100644 index 00000000000..751416fe397 --- /dev/null +++ b/compute/compute/recipes/instance_templates/create_from_instance.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/compute/recipes/instance_templates/create_with_subnet.py b/compute/compute/recipes/instance_templates/create_with_subnet.py new file mode 100644 index 00000000000..85639db006b --- /dev/null +++ b/compute/compute/recipes/instance_templates/create_with_subnet.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/compute/recipes/instance_templates/delete.py b/compute/compute/recipes/instance_templates/delete.py new file mode 100644 index 00000000000..bf774c57d63 --- /dev/null +++ b/compute/compute/recipes/instance_templates/delete.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/compute/recipes/instance_templates/get.py b/compute/compute/recipes/instance_templates/get.py new file mode 100644 index 00000000000..3036e73488a --- /dev/null +++ b/compute/compute/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/compute/recipes/instance_templates/list.py b/compute/compute/recipes/instance_templates/list.py new file mode 100644 index 00000000000..6ce5c5b73c0 --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/__init__.py b/compute/compute/recipes/instances/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/recipes/instances/create.py b/compute/compute/recipes/instances/create.py new file mode 100644 index 00000000000..b51a2e73779 --- /dev/null +++ b/compute/compute/recipes/instances/create.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 + +# +# + +# + +# + +# +# + +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/compute/recipes/instances/create_start_instance/__init__.py b/compute/compute/recipes/instances/create_start_instance/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/recipes/instances/create_start_instance/create_from_custom_image.py b/compute/compute/recipes/instances/create_start_instance/create_from_custom_image.py new file mode 100644 index 00000000000..e50d60367af --- /dev/null +++ b/compute/compute/recipes/instances/create_start_instance/create_from_custom_image.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/compute/recipes/instances/create_start_instance/create_from_public_image.py b/compute/compute/recipes/instances/create_start_instance/create_from_public_image.py new file mode 100644 index 00000000000..6f6f0ee042c --- /dev/null +++ b/compute/compute/recipes/instances/create_start_instance/create_from_public_image.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/compute/recipes/instances/create_start_instance/create_from_snapshot.py b/compute/compute/recipes/instances/create_start_instance/create_from_snapshot.py new file mode 100644 index 00000000000..2047eeb57bd --- /dev/null +++ b/compute/compute/recipes/instances/create_start_instance/create_from_snapshot.py @@ -0,0 +1,26 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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/compute/recipes/instances/create_start_instance/create_with_additional_disk.py b/compute/compute/recipes/instances/create_start_instance/create_with_additional_disk.py new file mode 100644 index 00000000000..ab9baa6e4c8 --- /dev/null +++ b/compute/compute/recipes/instances/create_start_instance/create_with_additional_disk.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/compute/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.py b/compute/compute/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.py new file mode 100644 index 00000000000..858e6188422 --- /dev/null +++ b/compute/compute/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.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/compute/recipes/instances/create_with_subnet.py b/compute/compute/recipes/instances/create_with_subnet.py new file mode 100644 index 00000000000..906edca50d7 --- /dev/null +++ b/compute/compute/recipes/instances/create_with_subnet.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/compute/recipes/instances/custom_hostname/create.py b/compute/compute/recipes/instances/custom_hostname/create.py new file mode 100644 index 00000000000..55f3b47e679 --- /dev/null +++ b/compute/compute/recipes/instances/custom_hostname/create.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/compute/recipes/instances/custom_hostname/get.py b/compute/compute/recipes/instances/custom_hostname/get.py new file mode 100644 index 00000000000..d69cce045fa --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/custom_machine_types/__init__.py b/compute/compute/recipes/instances/custom_machine_types/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/recipes/instances/custom_machine_types/create_shared_with_helper.py b/compute/compute/recipes/instances/custom_machine_types/create_shared_with_helper.py new file mode 100644 index 00000000000..6adc8009821 --- /dev/null +++ b/compute/compute/recipes/instances/custom_machine_types/create_shared_with_helper.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/compute/recipes/instances/custom_machine_types/create_with_helper.py b/compute/compute/recipes/instances/custom_machine_types/create_with_helper.py new file mode 100644 index 00000000000..0ea883cf5a5 --- /dev/null +++ b/compute/compute/recipes/instances/custom_machine_types/create_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/compute/recipes/instances/custom_machine_types/create_without_helper.py b/compute/compute/recipes/instances/custom_machine_types/create_without_helper.py new file mode 100644 index 00000000000..e88388a82d2 --- /dev/null +++ b/compute/compute/recipes/instances/custom_machine_types/create_without_helper.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/compute/recipes/instances/custom_machine_types/extra_mem_no_helper.py b/compute/compute/recipes/instances/custom_machine_types/extra_mem_no_helper.py new file mode 100644 index 00000000000..68fdc27590b --- /dev/null +++ b/compute/compute/recipes/instances/custom_machine_types/extra_mem_no_helper.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/compute/recipes/instances/custom_machine_types/helper_class.py b/compute/compute/recipes/instances/custom_machine_types/helper_class.py new file mode 100644 index 00000000000..e5a48c0c962 --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/custom_machine_types/update_memory.py b/compute/compute/recipes/instances/custom_machine_types/update_memory.py new file mode 100644 index 00000000000..5817cd987dd --- /dev/null +++ b/compute/compute/recipes/instances/custom_machine_types/update_memory.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/compute/recipes/instances/delete.py b/compute/compute/recipes/instances/delete.py new file mode 100644 index 00000000000..68fc7f5546b --- /dev/null +++ b/compute/compute/recipes/instances/delete.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/compute/recipes/instances/delete_protection/__init__.py b/compute/compute/recipes/instances/delete_protection/__init__.py new file mode 100644 index 00000000000..a3ded82a3b6 --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/delete_protection/create.py b/compute/compute/recipes/instances/delete_protection/create.py new file mode 100644 index 00000000000..f1bb3a9b39a --- /dev/null +++ b/compute/compute/recipes/instances/delete_protection/create.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/compute/recipes/instances/delete_protection/get.py b/compute/compute/recipes/instances/delete_protection/get.py new file mode 100644 index 00000000000..1d7697dd103 --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/delete_protection/set.py b/compute/compute/recipes/instances/delete_protection/set.py new file mode 100644 index 00000000000..785e8f7813f --- /dev/null +++ b/compute/compute/recipes/instances/delete_protection/set.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/compute/recipes/instances/from_instance_template/__init__.py b/compute/compute/recipes/instances/from_instance_template/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/recipes/instances/from_instance_template/create_from_template.py b/compute/compute/recipes/instances/from_instance_template/create_from_template.py new file mode 100644 index 00000000000..c296366cc90 --- /dev/null +++ b/compute/compute/recipes/instances/from_instance_template/create_from_template.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/compute/recipes/instances/from_instance_template/create_from_template_with_overrides.py b/compute/compute/recipes/instances/from_instance_template/create_from_template_with_overrides.py new file mode 100644 index 00000000000..27d1b2ae080 --- /dev/null +++ b/compute/compute/recipes/instances/from_instance_template/create_from_template_with_overrides.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/compute/recipes/instances/list.py b/compute/compute/recipes/instances/list.py new file mode 100644 index 00000000000..92aff46b1f5 --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/list_all.py b/compute/compute/recipes/instances/list_all.py new file mode 100644 index 00000000000..e1fafd7f2aa --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/preemptible/__init__.py b/compute/compute/recipes/instances/preemptible/__init__.py new file mode 100644 index 00000000000..a3ded82a3b6 --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/preemptible/create_preemptible.py b/compute/compute/recipes/instances/preemptible/create_preemptible.py new file mode 100644 index 00000000000..a6161541211 --- /dev/null +++ b/compute/compute/recipes/instances/preemptible/create_preemptible.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/compute/recipes/instances/preemptible/is_preemptible.py b/compute/compute/recipes/instances/preemptible/is_preemptible.py new file mode 100644 index 00000000000..d57031c83fe --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/preemptible/preemption_history.py b/compute/compute/recipes/instances/preemptible/preemption_history.py new file mode 100644 index 00000000000..0c9a9a8ce6d --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/reset.py b/compute/compute/recipes/instances/reset.py new file mode 100644 index 00000000000..0842ed544b5 --- /dev/null +++ b/compute/compute/recipes/instances/reset.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/compute/recipes/instances/start.py b/compute/compute/recipes/instances/start.py new file mode 100644 index 00000000000..9ea6be08a88 --- /dev/null +++ b/compute/compute/recipes/instances/start.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/compute/recipes/instances/start_encrypted.py b/compute/compute/recipes/instances/start_encrypted.py new file mode 100644 index 00000000000..6833c644eff --- /dev/null +++ b/compute/compute/recipes/instances/start_encrypted.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/compute/recipes/instances/stop.py b/compute/compute/recipes/instances/stop.py new file mode 100644 index 00000000000..7dda8bcfab0 --- /dev/null +++ b/compute/compute/recipes/instances/stop.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/compute/recipes/operations/__init__.py b/compute/compute/recipes/operations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/recipes/operations/operation_check.py b/compute/compute/recipes/operations/operation_check.py new file mode 100644 index 00000000000..8913e7324cc --- /dev/null +++ b/compute/compute/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/compute/recipes/usage_report/__init__.py b/compute/compute/recipes/usage_report/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/recipes/usage_report/usage_reports.py b/compute/compute/recipes/usage_report/usage_reports.py new file mode 100644 index 00000000000..4a293b8007b --- /dev/null +++ b/compute/compute/recipes/usage_report/usage_reports.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 +""" +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/compute/requirements-test.txt b/compute/compute/requirements-test.txt index 5737e358445..45720ccd4e4 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -1,4 +1,5 @@ pytest==7.0.1 +pytest-parallel==0.1.1 flaky==3.7.0 google-cloud-storage==2.1.0; python_version == '3.6' google-cloud-storage==2.1.0; python_version >= '3.7' \ No newline at end of file diff --git a/compute/compute/sgs.py b/compute/compute/sgs.py index f7d4f43c883..f8278f7b561 100644 --- a/compute/compute/sgs.py +++ b/compute/compute/sgs.py @@ -45,9 +45,9 @@ # directory and apply your changes there. """ -DEFAULT_OUTPUT_PATH = Path("sgs_test_fixtures/output") -INGREDIENTS_PATH = Path("sgs_test_fixtures/ingredients") -RECIPES_PATH = Path("sgs_test_fixtures/recipes") +DEFAULT_OUTPUT_PATH = Path("snippets") +INGREDIENTS_PATH = Path("ingredients") +RECIPES_PATH = Path("recipes") @dataclass @@ -133,7 +133,10 @@ def load_ingredient(path: Path) -> Ingredient: ingredient_name = INGREDIENTS_START.match(line).group(1) in_ingredient = True else: - warnings.warn(f"The ingredient in {path} has no closing tag.", SyntaxWarning) + if in_ingredient: + warnings.warn( + f"The ingredient in {path} has no closing tag.", SyntaxWarning + ) return Ingredient( name=ingredient_name, text="".join(ingredient_lines), @@ -164,7 +167,7 @@ def load_recipes(path: Path) -> dict: if ipath.is_dir(): recipes.update(load_recipes(ipath)) elif ipath.is_file(): - recipes[ipath] = load_recipe(ipath) + recipes[ipath.absolute()] = load_recipe(ipath) return recipes @@ -254,10 +257,9 @@ def save_rendered_recipe( output_dir: Path = DEFAULT_OUTPUT_PATH, recipes_path: Path = RECIPES_PATH, ) -> Path: - output_dir.mkdir(exist_ok=True) - + output_dir.mkdir(parents=True, exist_ok=True) output_path = output_dir / recipe_path.relative_to(recipes_path) - output_path.parent.mkdir(exist_ok=True) + output_path.parent.mkdir(parents=True, exist_ok=True) with output_path.open(mode="w") as out_file: out_file.write(rendered_recipe) @@ -282,7 +284,12 @@ def generate( for path, recipe in recipes.items(): rendered = render_recipe(recipe, ingredients) - out = save_rendered_recipe(path, rendered, output_dir=Path(args.output_dir)) + 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:") diff --git a/compute/compute/snippets/__init__.py b/compute/compute/snippets/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/snippets/firewall/__init__.py b/compute/compute/snippets/firewall/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/snippets/firewall/create.py b/compute/compute/snippets/firewall/create.py new file mode 100644 index 00000000000..5bcf1c5fce9 --- /dev/null +++ b/compute/compute/snippets/firewall/create.py @@ -0,0 +1,74 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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] +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} + """ + 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: + + # firewall_rule.priority = 0 + + firewall_client = compute_v1.FirewallsClient() + op = firewall_client.insert_unary( + project=project_id, firewall_resource=firewall_rule + ) + + op_client = compute_v1.GlobalOperationsClient() + op_client.wait(project=project_id, operation=op.name) + + return firewall_client.get(project=project_id, firewall=firewall_rule_name) + + +# [END compute_firewall_create] diff --git a/compute/compute/snippets/firewall/delete.py b/compute/compute/snippets/firewall/delete.py new file mode 100644 index 00000000000..8dbea87095e --- /dev/null +++ b/compute/compute/snippets/firewall/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. +# 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] +from google.cloud import compute_v1 + + +def delete_firewall_rule(project_id: str, firewall_rule_name: str): + """ + Deleted 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_unary( + project=project_id, firewall=firewall_rule_name + ) + + operation_client = compute_v1.GlobalOperationsClient() + operation_client.wait(project=project_id, operation=operation.name) + return + + +# [END compute_firewall_delete] diff --git a/compute/compute/snippets/firewall/list.py b/compute/compute/snippets/firewall/list.py new file mode 100644 index 00000000000..d4553ac1292 --- /dev/null +++ b/compute/compute/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/compute/snippets/sample_firewall.py b/compute/compute/snippets/firewall/main.py similarity index 82% rename from compute/compute/snippets/sample_firewall.py rename to compute/compute/snippets/firewall/main.py index c2bdd3fa75a..d8b47a3380f 100644 --- a/compute/compute/snippets/sample_firewall.py +++ b/compute/compute/snippets/firewall/main.py @@ -1,65 +1,32 @@ -# Copyright 2021 Google LLC +# Copyright 2022 Google LLC # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 - -# [START compute_firewall_list] -# [START compute_firewall_create] -# [START compute_firewall_patch] -# [START compute_firewall_delete] -import google.cloud.compute_v1 as compute_v1 - -# [END compute_firewall_delete] -# [END compute_firewall_patch] -# [END compute_firewall_create] -# [END compute_firewall_list] - - -# [START compute_firewall_list] -def list_firewall_rules(project_id: str) -> Iterable: - """ - 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 +# 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. -# [END compute_firewall_list] - +from typing import Iterable -def get_firewall_rule(project_id: str, firewall_rule_name: str) -> compute_v1.Firewall: - firewall_client = compute_v1.FirewallsClient() - return firewall_client.get(project=project_id, firewall=firewall_rule_name) +from google.cloud import compute_v1 -# [START compute_firewall_create] 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. @@ -102,30 +69,20 @@ def create_firewall_rule( op_client = compute_v1.GlobalOperationsClient() op_client.wait(project=project_id, operation=op.name) - return - - -# [END compute_firewall_create] + return firewall_client.get(project=project_id, firewall=firewall_rule_name) -# [START compute_firewall_patch] -def patch_firewall_priority(project_id: str, firewall_rule_name: str, priority: int): +def delete_firewall_rule(project_id: str, firewall_rule_name: str): """ - Modifies the priority of a given firewall rule. + Deleted 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 rule you want to modify. - priority: the new priority to be set for the rule. + firewall_rule_name: name of the firewall rule you want to delete. """ - 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_unary( - project=project_id, firewall=firewall_rule_name, firewall_resource=firewall_rule + operation = firewall_client.delete_unary( + project=project_id, firewall=firewall_rule_name ) operation_client = compute_v1.GlobalOperationsClient() @@ -133,21 +90,48 @@ def patch_firewall_priority(project_id: str, firewall_rule_name: str, priority: return -# [END compute_firewall_patch] +def get_firewall_rule(project_id: str, firewall_rule_name: str) -> compute_v1.Firewall: + firewall_client = compute_v1.FirewallsClient() + return firewall_client.get(project=project_id, firewall=firewall_rule_name) -# [START compute_firewall_delete] -def delete_firewall_rule(project_id: str, firewall_rule_name: str): +def list_firewall_rules(project_id: str) -> Iterable[compute_v1.Firewall]: """ - Deleted a firewall rule from the project. + 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. - firewall_rule_name: name of the firewall rule you want to delete. + + Returns: + A flat list of all firewall rules defined for given project. """ firewall_client = compute_v1.FirewallsClient() - operation = firewall_client.delete_unary( - project=project_id, firewall=firewall_rule_name + 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): + """ + 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_unary( + project=project_id, firewall=firewall_rule_name, firewall_resource=firewall_rule ) operation_client = compute_v1.GlobalOperationsClient() @@ -155,9 +139,6 @@ def delete_firewall_rule(project_id: str, firewall_rule_name: str): return -# [END compute_firewall_delete] - - if __name__ == "__main__": import google.auth import google.auth.exceptions diff --git a/compute/compute/snippets/firewall/patch.py b/compute/compute/snippets/firewall/patch.py new file mode 100644 index 00000000000..e5f2a96d676 --- /dev/null +++ b/compute/compute/snippets/firewall/patch.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 + + +# 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] +from google.cloud import compute_v1 + + +def patch_firewall_priority(project_id: str, firewall_rule_name: str, priority: int): + """ + 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_unary( + project=project_id, firewall=firewall_rule_name, firewall_resource=firewall_rule + ) + + operation_client = compute_v1.GlobalOperationsClient() + operation_client.wait(project=project_id, operation=operation.name) + return + + +# [END compute_firewall_patch] diff --git a/compute/compute/snippets/images/__init__.py b/compute/compute/snippets/images/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/snippets/images/get.py b/compute/compute/snippets/images/get.py new file mode 100644 index 00000000000..94c8f3af468 --- /dev/null +++ b/compute/compute/snippets/images/get.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. +# 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: + 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/compute/snippets/images/list.py b/compute/compute/snippets/images/list.py new file mode 100644 index 00000000000..3618295331f --- /dev/null +++ b/compute/compute/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/compute/snippets/sample_pagination.py b/compute/compute/snippets/images/pagination.py similarity index 85% rename from compute/compute/snippets/sample_pagination.py rename to compute/compute/snippets/images/pagination.py index e2590b54160..7ac6d0b6e59 100644 --- a/compute/compute/snippets/sample_pagination.py +++ b/compute/compute/snippets/images/pagination.py @@ -14,6 +14,12 @@ # 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 @@ -23,7 +29,7 @@ # [START compute_images_list] -def print_images_list(project: str) -> None: +def print_images_list(project: str) -> str: """ Prints a list of all non-deprecated image names available in given project. @@ -31,26 +37,29 @@ def print_images_list(project: str) -> None: project: project ID or project number of the Cloud project you want to list images from. Returns: - None. + 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) -> None: +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. @@ -60,13 +69,14 @@ def print_images_list_by_page(project: str, page_size: int = 10) -> None: page_size: size of the pages you want the API to return on each call. Returns: - None. + 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 @@ -75,8 +85,11 @@ def print_images_list_by_page(project: str, page_size: int = 10) -> None: 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] diff --git a/compute/compute/snippets/instance_templates/__init__.py b/compute/compute/snippets/instance_templates/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/snippets/instance_templates/create.py b/compute/compute/snippets/instance_templates/create.py new file mode 100644 index 00000000000..f328bbfc130 --- /dev/null +++ b/compute/compute/snippets/instance_templates/create.py @@ -0,0 +1,78 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 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_client = compute_v1.GlobalOperationsClient() + op = template_client.insert_unary( + project=project_id, instance_template_resource=template + ) + operation_client.wait(project=project_id, operation=op.name) + + return template_client.get(project=project_id, instance_template=template_name) + + +# [END compute_template_create] diff --git a/compute/compute/snippets/instance_templates/create_from_instance.py b/compute/compute/snippets/instance_templates/create_from_instance.py new file mode 100644 index 00000000000..20afc715435 --- /dev/null +++ b/compute/compute/snippets/instance_templates/create_from_instance.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 + + +# 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] +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_client = compute_v1.GlobalOperationsClient() + op = template_client.insert_unary( + project=project_id, instance_template_resource=template + ) + operation_client.wait(project=project_id, operation=op.name) + + return template_client.get(project=project_id, instance_template=template_name) + + +# [END compute_template_create_from_instance] diff --git a/compute/compute/snippets/instance_templates/create_with_subnet.py b/compute/compute/snippets/instance_templates/create_with_subnet.py new file mode 100644 index 00000000000..ea6ddc19166 --- /dev/null +++ b/compute/compute/snippets/instance_templates/create_with_subnet.py @@ -0,0 +1,77 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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] +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_client = compute_v1.GlobalOperationsClient() + op = template_client.insert_unary( + project=project_id, instance_template_resource=template + ) + operation_client.wait(project=project_id, operation=op.name) + + return template_client.get(project=project_id, instance_template=template_name) + + +# [END compute_template_create_with_subnet] diff --git a/compute/compute/snippets/instance_templates/delete.py b/compute/compute/snippets/instance_templates/delete.py new file mode 100644 index 00000000000..b0700c9abdb --- /dev/null +++ b/compute/compute/snippets/instance_templates/delete.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_template_delete] +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_client = compute_v1.GlobalOperationsClient() + op = template_client.delete_unary( + project=project_id, instance_template=template_name + ) + operation_client.wait(project=project_id, operation=op.name) + return + + +# [END compute_template_delete] diff --git a/compute/compute/snippets/instance_templates/get.py b/compute/compute/snippets/instance_templates/get.py new file mode 100644 index 00000000000..439b3bea979 --- /dev/null +++ b/compute/compute/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/compute/snippets/instance_templates/list.py b/compute/compute/snippets/instance_templates/list.py new file mode 100644 index 00000000000..495686c62d8 --- /dev/null +++ b/compute/compute/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/compute/snippets/instances/__init__.py b/compute/compute/snippets/instances/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/snippets/instances/create.py b/compute/compute/snippets/instances/create.py new file mode 100644 index 00000000000..73d2d806cfe --- /dev/null +++ b/compute/compute/snippets/instances/create.py @@ -0,0 +1,195 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 List + +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + 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 +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk + + +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, + preemptible: bool = False, + 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +# [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/compute/snippets/instances/create_start_instance/__init__.py b/compute/compute/snippets/instances/create_start_instance/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py b/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py new file mode 100644 index 00000000000..0cea574075a --- /dev/null +++ b/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py @@ -0,0 +1,193 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 List + +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + 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 +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk + + +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, + preemptible: bool = False, + 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +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)] + instance = create_instance(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_custom_image] diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py b/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py new file mode 100644 index 00000000000..0d309604a46 --- /dev/null +++ b/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py @@ -0,0 +1,192 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 List + +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + 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 +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk + + +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, + preemptible: bool = False, + 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +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)] + instance = create_instance(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_image] diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py b/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py new file mode 100644 index 00000000000..bc7b01c2600 --- /dev/null +++ b/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py @@ -0,0 +1,185 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 List + +from google.cloud import compute_v1 + + +def disk_from_snapshot( + disk_type: str, disk_size_gb: int, boot: bool, disk_snapshot: str +) -> 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 + disk_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}" + + Returns: + AttachedDisk object configured to be created using the specified snapshot. + """ + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_snapshot = disk_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 = True + disk.boot = boot + return disk + + +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, + preemptible: bool = False, + 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +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. + + 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, 11, True, snapshot_link)] + instance = create_instance(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_snapshot] diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py b/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py new file mode 100644 index 00000000000..7945638de8a --- /dev/null +++ b/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py @@ -0,0 +1,222 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 List + +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + 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 +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk + + +def empty_disk(disk_type: str, disk_size_gb: int) -> 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 + + 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 = True + disk.boot = False + return disk + + +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, + preemptible: bool = False, + 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +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 and a 11 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, 10, True, newest_debian.self_link), + empty_disk(disk_type, 11), + ] + instance = create_instance(project_id, zone, instance_name, disks) + return instance + + +# [END compute_instances_create_from_image_plus_empty_disk] diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py b/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py new file mode 100644 index 00000000000..134f41c0153 --- /dev/null +++ b/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py @@ -0,0 +1,229 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 List + +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + 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 +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk + + +def disk_from_snapshot( + disk_type: str, disk_size_gb: int, boot: bool, disk_snapshot: str +) -> 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 + disk_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}" + + Returns: + AttachedDisk object configured to be created using the specified snapshot. + """ + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_snapshot = disk_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 = True + disk.boot = boot + return disk + + +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, + preemptible: bool = False, + 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +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/compute/snippets/instances/create_with_subnet.py b/compute/compute/snippets/instances/create_with_subnet.py new file mode 100644 index 00000000000..63e46d0ccf4 --- /dev/null +++ b/compute/compute/snippets/instances/create_with_subnet.py @@ -0,0 +1,205 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 List + +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + 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 +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk + + +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, + preemptible: bool = False, + 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +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/compute/snippets/instances/custom_hostname/create.py b/compute/compute/snippets/instances/custom_hostname/create.py new file mode 100644 index 00000000000..c600f6c17f4 --- /dev/null +++ b/compute/compute/snippets/instances/custom_hostname/create.py @@ -0,0 +1,195 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 List + +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + 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 +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk + + +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, + preemptible: bool = False, + 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +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/compute/snippets/instances/custom_hostname/get.py b/compute/compute/snippets/instances/custom_hostname/get.py new file mode 100644 index 00000000000..673d5c81066 --- /dev/null +++ b/compute/compute/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/compute/snippets/instances/custom_machine_types/__init__.py b/compute/compute/snippets/instances/custom_machine_types/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py new file mode 100644 index 00000000000..16d10788391 --- /dev/null +++ b/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py @@ -0,0 +1,390 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 List + +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", + ], + ) + + 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] + self.core_count = 2 if self.is_shared() else core_count + self.memory_mb = memory_mb + + self._check() + 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: + # Assuming this runs after _check() and the total memory requested is correct + return self.memory_mb > self.core_count * self.limits.max_mem_per_core + + def _check(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." + ) + + 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: + 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 +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk + + +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, + preemptible: bool = False, + 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +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/compute/snippets/sample_custom_types.py b/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py similarity index 54% rename from compute/compute/snippets/sample_custom_types.py rename to compute/compute/snippets/instances/custom_machine_types/create_with_helper.py index 38ad64b8171..79e7d1a2a01 100644 --- a/compute/compute/snippets/sample_custom_types.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py @@ -1,4 +1,4 @@ -# Copyright 2021 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,20 +11,25 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# [START compute_custom_machine_type_create ] +# 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, unique +from enum import Enum +from enum import unique +import re import sys -import time -from typing import Union +from typing import List from google.cloud import compute_v1 -# [END compute_custom_machine_type_create ] - - -# [START compute_custom_machine_type_helper_class ] def gb_to_mb(value: int) -> int: return value << 10 @@ -206,16 +211,58 @@ def from_str(cls, machine_type: str): return cls(zone, cpu, memory, cores) -# [END compute_custom_machine_type_helper_class ] +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + 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 +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk -# [START compute_custom_machine_type_create ] def create_instance( project_id: str, zone: str, instance_name: str, - machine_type: Union[str, "CustomMachineType"], -): + disks: List[compute_v1.AttachedDisk], + machine_type: str = "n1-standard-1", + network_link: str = "global/networks/default", + subnetwork_link: str = None, + preemptible: bool = False, + 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. @@ -226,39 +273,63 @@ def create_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" - OR - It can be a CustomMachineType object, describing a custom type - you want to use. - - Return: + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() operation_client = compute_v1.ZoneOperationsClient() - # Describe 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-10" - ) - initialize_params.disk_size_gb = 10 - disk.initialize_params = initialize_params - disk.auto_delete = True - disk.boot = True - disk.type_ = compute_v1.AttachedDisk.Type.PERSISTENT.name - - # Use the network interface provided in the network_name argument. + # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() - network_interface.name = "global/networks/default" + network_interface.name = network_link + if subnetwork_link: + network_interface.subnetwork = subnetwork_link # Collect information into the Instance object. instance = compute_v1.Instance() instance.name = instance_name - instance.disks = [disk] - instance.machine_type = str(machine_type) + 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}" + instance.network_interfaces = [network_interface] + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = True + # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -266,9 +337,8 @@ def create_instance( request.instance_resource = instance # Wait for the create operation to complete. - print( - f"Creating the {instance_name} instance of type {instance.machine_type} in {zone}..." - ) + print(f"Creating the {instance_name} instance in {zone}...") + operation = instance_client.insert_unary(request=request) while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( @@ -279,13 +349,9 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance_client.get(project=project_id, zone=zone, instance=instance.name) + return instance -# [END compute_custom_machine_type_create ] - - -# [START compute_custom_machine_type_create_with_helper ] def create_custom_instance( project_id: str, zone: str, @@ -316,216 +382,12 @@ def create_custom_instance( CustomMachineType.CPUSeries.N2D, ) custom_type = CustomMachineType(zone, cpu_series, memory, core_count) - return create_instance(project_id, zone, instance_name, custom_type) - - -# [END compute_custom_machine_type_create_with_helper ] - - -# [START compute_custom_machine_type_create_shared_with_helper ] -def create_custom_shared_core_instance( - project_id: str, - zone: str, - instance_name: str, - cpu_series: CustomMachineType.CPUSeries, - memory: int, -): - """ - 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) - return create_instance(project_id, zone, instance_name, custom_type) - - -# [END compute_custom_machine_type_create_shared_with_helper ] - - -# [START compute_custom_machine_type_create_without_helper ] -def create_custom_instances_no_helper( - project_id: str, zone: str, instance_name: str, core_count: int, memory: int -): - """ - Create 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. - """ - # 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", - f"zones/{zone}/machineTypes/custom-{core_count}-{memory}", - ), - create_instance( - project_id, - zone, - f"{instance_name}_n2", - f"zones/{zone}/machineTypes/n2-custom-{core_count}-{memory}", - ), - create_instance( - project_id, - zone, - f"{instance_name}_n2d", - f"zones/{zone}/machineTypes/n2d-custom-{core_count}-{memory}", - ), - create_instance( - project_id, - zone, - f"{instance_name}_e2", - f"zones/{zone}/machineTypes/e2-custom-{core_count}-{memory}", - ), - create_instance( - project_id, - zone, - f"{instance_name}_e2_micro", - f"zones/{zone}/machineTypes/e2-custom-micro-{memory}", - ), - create_instance( - project_id, - zone, - f"{instance_name}_e2_small", - f"zones/{zone}/machineTypes/e2-custom-small-{memory}", - ), - create_instance( - project_id, - zone, - f"{instance_name}_e2_medium", - f"zones/{zone}/machineTypes/e2-custom-medium-{memory}", - ), - ] - return instances - - -# [END compute_custom_machine_type_create_without_helper ] - - -# [START compute_custom_machine_type_extra_mem_no_helper ] -def create_custom_instances_extra_mem_no_helper( - project_id: str, zone: str, instance_name: str, core_count: int, memory: int -): - """ - Create 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. - """ - # 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", - f"zones/{zone}/machineTypes/custom-{core_count}-{memory}-ext", - ), - create_instance( - project_id, - zone, - f"{instance_name}_n2_extra_mem", - f"zones/{zone}/machineTypes/n2-custom-{core_count}-{memory}-ext", - ), - create_instance( - project_id, - zone, - f"{instance_name}_n2d_extra_mem", - f"zones/{zone}/machineTypes/n2d-custom-{core_count}-{memory}-ext", - ), - ] - return instances - - -# [END compute_custom_machine_type_extra_mem_no_helper ] - - -# [START compute_custom_machine_type_update_memory ] -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() - operation_client = compute_v1.ZoneOperationsClient() - instance = instance_client.get( - project=project_id, zone=zone, instance=instance_name - ) - - # Make sure that the machine is turned off - if instance.status not in ( - instance.Status.TERMINATED.name, - instance.Status.STOPPED.name, - ): - op = instance_client.stop_unary( - project=project_id, zone=zone, instance=instance_name - ) - operation_client.wait(project=project_id, zone=zone, operation=op.name) - 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) - - # 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" - # 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) - op = instance_client.update_unary( - project=project_id, - zone=zone, - instance=instance_name, - instance_resource=instance, - ) - operation_client.wait(project=project_id, zone=zone, operation=op.name) + 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 instance_client.get(project=project_id, zone=zone, instance=instance_name) + return create_instance(project_id, zone, instance_name, disks, str(custom_type)) -# [END compute_custom_machine_type_update_memory ] +# [END compute_custom_machine_type_create_with_helper] diff --git a/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py new file mode 100644 index 00000000000..5f04cc79f6c --- /dev/null +++ b/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py @@ -0,0 +1,245 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 List + +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + 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 +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk + + +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, + preemptible: bool = False, + 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +def create_custom_instances_no_helper( + project_id: str, zone: str, instance_name: str, core_count: int, memory: int +) -> List[compute_v1.Instance]: + """ + Create 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)] + # 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", + disks, + f"zones/{zone}/machineTypes/custom-{core_count}-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2", + disks, + f"zones/{zone}/machineTypes/n2-custom-{core_count}-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2d", + disks, + f"zones/{zone}/machineTypes/n2d-custom-{core_count}-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_e2", + disks, + f"zones/{zone}/machineTypes/e2-custom-{core_count}-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_e2_micro", + disks, + f"zones/{zone}/machineTypes/e2-custom-micro-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_e2_small", + disks, + f"zones/{zone}/machineTypes/e2-custom-small-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_e2_medium", + disks, + f"zones/{zone}/machineTypes/e2-custom-medium-{memory}", + ), + ] + return instances + + +# [END compute_custom_machine_type_create_without_helper] diff --git a/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py b/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py new file mode 100644 index 00000000000..a2667437e56 --- /dev/null +++ b/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py @@ -0,0 +1,217 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 List + +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + 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 +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk + + +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, + preemptible: bool = False, + 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +def create_custom_instances_extra_mem_no_helper( + project_id: str, zone: str, instance_name: str, core_count: int, memory: int +) -> List[compute_v1.Instance]: + """ + Create 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/compute/snippets/instances/custom_machine_types/helper_class.py b/compute/compute/snippets/instances/custom_machine_types/helper_class.py new file mode 100644 index 00000000000..acd867c0ff1 --- /dev/null +++ b/compute/compute/snippets/instances/custom_machine_types/helper_class.py @@ -0,0 +1,209 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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", + ], + ) + + 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] + self.core_count = 2 if self.is_shared() else core_count + self.memory_mb = memory_mb + + self._check() + 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: + # Assuming this runs after _check() and the total memory requested is correct + return self.memory_mb > self.core_count * self.limits.max_mem_per_core + + def _check(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." + ) + + 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/compute/snippets/instances/custom_machine_types/update_memory.py b/compute/compute/snippets/instances/custom_machine_types/update_memory.py new file mode 100644 index 00000000000..5a168e784f6 --- /dev/null +++ b/compute/compute/snippets/instances/custom_machine_types/update_memory.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_custom_machine_type_update_memory] +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() + operation_client = compute_v1.ZoneOperationsClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + + # Make sure that the machine is turned off + if instance.status not in ( + instance.Status.TERMINATED.name, + instance.Status.STOPPED.name, + ): + op = instance_client.stop_unary( + project=project_id, zone=zone, instance=instance_name + ) + operation_client.wait(project=project_id, zone=zone, operation=op.name) + 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) + + # 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" + # 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) + op = instance_client.update_unary( + project=project_id, + zone=zone, + instance=instance_name, + instance_resource=instance, + ) + operation_client.wait(project=project_id, zone=zone, operation=op.name) + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +# [END compute_custom_machine_type_update_memory] diff --git a/compute/compute/snippets/instances/delete.py b/compute/compute/snippets/instances/delete.py new file mode 100644 index 00000000000..be8c714fe18 --- /dev/null +++ b/compute/compute/snippets/instances/delete.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. +# 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 + +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() + operation_client = compute_v1.ZoneOperationsClient() + + print(f"Deleting {machine_name} from {zone}...") + operation = instance_client.delete_unary( + project=project_id, zone=zone, instance=machine_name + ) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during deletion:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during deletion:", operation.warnings, file=sys.stderr) + print(f"Instance {machine_name} deleted.") + return + + +# [END compute_instances_delete] diff --git a/compute/compute/snippets/instances/delete_protection/__init__.py b/compute/compute/snippets/instances/delete_protection/__init__.py new file mode 100644 index 00000000000..a3ded82a3b6 --- /dev/null +++ b/compute/compute/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/compute/snippets/instances/delete_protection/create.py b/compute/compute/snippets/instances/delete_protection/create.py new file mode 100644 index 00000000000..b4b21b7fc23 --- /dev/null +++ b/compute/compute/snippets/instances/delete_protection/create.py @@ -0,0 +1,195 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 List + +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + 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 +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk + + +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, + preemptible: bool = False, + 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +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/compute/snippets/instances/delete_protection/get.py b/compute/compute/snippets/instances/delete_protection/get.py new file mode 100644 index 00000000000..d6ecfa398b0 --- /dev/null +++ b/compute/compute/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 state 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/compute/snippets/instances/delete_protection/set.py b/compute/compute/snippets/instances/delete_protection/set.py new file mode 100644 index 00000000000..e25269317d6 --- /dev/null +++ b/compute/compute/snippets/instances/delete_protection/set.py @@ -0,0 +1,52 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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] +from google.cloud import compute_v1 + + +def set_delete_protection( + project_id: str, zone: str, instance_name: str, delete_protection: bool +): + """ + 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 virtual machine to update. + delete_protection: boolean value indicating if the virtual machine should be + protected against deletion or not. + """ + instance_client = compute_v1.InstancesClient() + operation_client = compute_v1.ZoneOperationsClient() + + 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_unary(request) + operation_client.wait(project=project_id, zone=zone, operation=operation.name) + return + + +# [END compute_delete_protection_set] diff --git a/compute/compute/snippets/instances/from_instance_template/__init__.py b/compute/compute/snippets/instances/from_instance_template/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/snippets/instances/from_instance_template/create_from_template.py b/compute/compute/snippets/instances/from_instance_template/create_from_template.py new file mode 100644 index 00000000000..6310cfd112c --- /dev/null +++ b/compute/compute/snippets/instances/from_instance_template/create_from_template.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. +# 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] +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. + """ + operation_client = compute_v1.ZoneOperationsClient() + 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 + + op = instance_client.insert_unary(instance_insert_request) + operation_client.wait(project=project_id, zone=zone, operation=op.name) + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +# [END compute_instances_create_from_template] diff --git a/compute/compute/snippets/sample_instance_from_template.py b/compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py similarity index 67% rename from compute/compute/snippets/sample_instance_from_template.py rename to compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py index 30ef65dbae3..6f76d32900e 100644 --- a/compute/compute/snippets/sample_instance_from_template.py +++ b/compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py @@ -1,4 +1,4 @@ -# Copyright 2021 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,53 +11,18 @@ # WITHOUT 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 -# [START compute_instances_create_from_template] -# [START compute_instances_create_from_template_with_overrides] -from google.cloud import compute_v1 - -# [END compute_instances_create_from_template_with_overrides] - - -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. - """ - operation_client = compute_v1.ZoneOperationsClient() - 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 - - op = instance_client.insert_unary(instance_insert_request) - operation_client.wait(project=project_id, zone=zone, operation=op.name) - - return instance_client.get(project=project_id, zone=zone, instance=instance_name) +# 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. -# [END compute_instances_create_from_template] +# [START compute_instances_create_from_template_with_overrides] +from google.cloud import compute_v1 -# [START compute_instances_create_from_template_with_overrides] def create_instance_from_template_with_overrides( project_id: str, zone: str, @@ -80,7 +45,7 @@ def create_instance_from_template_with_overrides( - "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 - newDiskSourceImage: Path the the disk image you want to use for your new + 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. diff --git a/compute/compute/snippets/instances/list.py b/compute/compute/snippets/instances/list.py new file mode 100644 index 00000000000..45830c72ea4 --- /dev/null +++ b/compute/compute/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/compute/snippets/instances/list_all.py b/compute/compute/snippets/instances/list_all.py new file mode 100644 index 00000000000..9549de0f43a --- /dev/null +++ b/compute/compute/snippets/instances/list_all.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. +# 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 typing import Dict, Iterable + +from google.cloud import compute_v1 + + +def list_all_instances( + project_id: str, +) -> Dict[str, Iterable[compute_v1.Instance]]: + """ + Return 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() + # Use the `max_results` parameter to limit the number of results that the API returns per response page. + request = compute_v1.AggregatedListInstancesRequest() + request.project = project_id + request.max_results = 50 + + agg_list = instance_client.aggregated_list(request=request) + + all_instances = {} + 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] = 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/compute/snippets/instances/preemptible/__init__.py b/compute/compute/snippets/instances/preemptible/__init__.py new file mode 100644 index 00000000000..a3ded82a3b6 --- /dev/null +++ b/compute/compute/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/compute/snippets/instances/preemptible/create_preemptible.py b/compute/compute/snippets/instances/preemptible/create_preemptible.py new file mode 100644 index 00000000000..2f4569bc873 --- /dev/null +++ b/compute/compute/snippets/instances/preemptible/create_preemptible.py @@ -0,0 +1,192 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 List + +from google.cloud import compute_v1 + + +def get_image_from_family(project: str, family: str) -> compute_v1.Image: + 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 +) -> 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}" + + 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 = True + boot_disk.boot = boot + return boot_disk + + +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, + preemptible: bool = False, + 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. + 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" + disks: a list of compute_v1.AttachedDisk objects describing the disks + you want to attach to your new 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}" + preemptible: boolean value indicating if the new instance should be preemptible + or not. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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 + + # Collect information into the Instance object. + instance = compute_v1.Instance() + 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}" + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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 + + # Shielded Instance settings + # Values presented here are the defaults. + # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() + # instance.shielded_instance_config.enable_secure_boot = False + # instance.shielded_instance_config.enable_vtpm = True + # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance + + +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/compute/snippets/instances/preemptible/is_preemptible.py b/compute/compute/snippets/instances/preemptible/is_preemptible.py new file mode 100644 index 00000000000..8a0c966fdfc --- /dev/null +++ b/compute/compute/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/compute/snippets/instances/preemptible/preemption_history.py b/compute/compute/snippets/instances/preemptible/preemption_history.py new file mode 100644 index 00000000000..23a1f79745d --- /dev/null +++ b/compute/compute/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/compute/snippets/instances/reset.py b/compute/compute/snippets/instances/reset.py new file mode 100644 index 00000000000..74bb94c2fbb --- /dev/null +++ b/compute/compute/snippets/instances/reset.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_reset_instance] +from google.cloud import compute_v1 + + +def reset_instance(project_id: str, zone: str, instance_name: str): + """ + 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() + op_client = compute_v1.ZoneOperationsClient() + + op = instance_client.reset_unary( + project=project_id, zone=zone, instance=instance_name + ) + + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait(operation=op.name, zone=zone, project=project_id) + return + + +# [END compute_reset_instance] diff --git a/compute/compute/snippets/instances/start.py b/compute/compute/snippets/instances/start.py new file mode 100644 index 00000000000..9de984bef5d --- /dev/null +++ b/compute/compute/snippets/instances/start.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_start_instance] +from google.cloud import compute_v1 + + +def start_instance(project_id: str, zone: str, instance_name: str): + """ + 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() + op_client = compute_v1.ZoneOperationsClient() + + op = instance_client.start_unary( + project=project_id, zone=zone, instance=instance_name + ) + + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait(operation=op.name, zone=zone, project=project_id) + return + + +# [END compute_start_instance] diff --git a/compute/compute/snippets/instances/start_encrypted.py b/compute/compute/snippets/instances/start_encrypted.py new file mode 100644 index 00000000000..c0d5c14e88c --- /dev/null +++ b/compute/compute/snippets/instances/start_encrypted.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 + + +# 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] +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() + op_client = compute_v1.ZoneOperationsClient() + + 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] + + op = instance_client.start_with_encryption_key_unary( + project=project_id, + zone=zone, + instance=instance_name, + instances_start_with_encryption_key_request_resource=enc_data, + ) + + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait(operation=op.name, zone=zone, project=project_id) + return + + +# [END compute_start_enc_instance] diff --git a/compute/compute/snippets/instances/stop.py b/compute/compute/snippets/instances/stop.py new file mode 100644 index 00000000000..cf155c74c5b --- /dev/null +++ b/compute/compute/snippets/instances/stop.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_stop_instance] +from google.cloud import compute_v1 + + +def stop_instance(project_id: str, zone: str, instance_name: str): + """ + Stops a stopped 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() + op_client = compute_v1.ZoneOperationsClient() + + op = instance_client.stop_unary( + project=project_id, zone=zone, instance=instance_name + ) + + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait(operation=op.name, zone=zone, project=project_id) + return + + +# [END compute_stop_instance] diff --git a/compute/compute/snippets/operations/__init__.py b/compute/compute/snippets/operations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/snippets/operations/operation_check.py b/compute/compute/snippets/operations/operation_check.py new file mode 100644 index 00000000000..d136372c39c --- /dev/null +++ b/compute/compute/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/compute/snippets/quickstart.py b/compute/compute/snippets/quickstart.py deleted file mode 100644 index 3303cc31703..00000000000 --- a/compute/compute/snippets/quickstart.py +++ /dev/null @@ -1,278 +0,0 @@ -#!/usr/bin/env python - -# 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. -""" -A sample script showing how to create, list and delete Google Compute Engine -instances using the google-cloud-compute library. It can be run from command -line to create, list and delete an instance in a given project in a given zone. -""" - -# [START compute_instances_create] -# [START compute_instances_delete] -import re -import sys - -# [START compute_instances_list] -# [START compute_instances_list_all] -# [START compute_instances_operation_check] -import typing - -import google.cloud.compute_v1 as compute_v1 - -# [END compute_instances_operation_check] -# [END compute_instances_list_all] -# [END compute_instances_list] -# [END compute_instances_delete] -# [END compute_instances_create] - - -# [START compute_instances_list] -def list_instances(project_id: str, zone: str) -> typing.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] - - -# [START compute_instances_list_all] -def list_all_instances( - project_id: str, -) -> typing.Dict[str, typing.Iterable[compute_v1.Instance]]: - """ - Return 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() - # Use the `max_results` parameter to limit the number of results that the API returns per response page. - request = compute_v1.AggregatedListInstancesRequest( - project=project_id, max_results=5 - ) - agg_list = instance_client.aggregated_list(request=request) - all_instances = {} - 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] = 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] - - -# [START compute_instances_create] -def create_instance( - project_id: str, - zone: str, - instance_name: str, - machine_type: str = "n1-standard-1", - source_image: str = "projects/debian-cloud/global/images/family/debian-10", - network_name: str = "global/networks/default", -) -> 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 you want to use. For example: “us-west3-b” - instance_name: name of the new virtual machine. - 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" - source_image: path to the operating system image to mount on your boot - 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. - network_name: name of the network you want the new instance to use. - For example: "global/networks/default" represents the `default` - network interface, which is created automatically for each project. - Returns: - Instance object. - """ - instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() - - # Describe 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 = ( - 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" - - # Use the network interface provided in the network_name argument. - network_interface = compute_v1.NetworkInterface() - network_interface.name = network_name - - # Collect information into the Instance object. - instance = compute_v1.Instance() - instance.name = instance_name - instance.disks = [disk] - 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}" - instance.network_interfaces = [network_interface] - - # 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_unary(request=request) - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) - print(f"Instance {instance_name} created.") - return instance - - -# [END compute_instances_create] - - -# [START compute_instances_delete] -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() - operation_client = compute_v1.ZoneOperationsClient() - - print(f"Deleting {machine_name} from {zone}...") - operation = instance_client.delete_unary( - project=project_id, zone=zone, instance=machine_name - ) - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if operation.error: - print("Error during deletion:", operation.error, file=sys.stderr) - if operation.warnings: - print("Warning during deletion:", operation.warnings, file=sys.stderr) - print(f"Instance {machine_name} deleted.") - return - - -# [END compute_instances_delete] - - -# [START compute_instances_operation_check] -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] - - -def main(project_id: str, zone: str, instance_name: str) -> None: - - create_instance(project_id, zone, instance_name) - - zone_instances = list_instances(project_id, zone) - print(f"Instances found in {zone}:", ", ".join(i.name for i in zone_instances)) - - all_instances = list_all_instances(project_id) - print(f"Instances found in project {project_id}:") - for i_zone, instances in all_instances.items(): - print(f"{i_zone}:", ", ".join(i.name for i in instances)) - - delete_instance(project_id, zone, instance_name) - - -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" - main(default_project_id, instance_zone, instance_name) diff --git a/compute/compute/snippets/sample_create_vm.py b/compute/compute/snippets/sample_create_vm.py deleted file mode 100644 index 1ea6ba2d2e6..00000000000 --- a/compute/compute/snippets/sample_create_vm.py +++ /dev/null @@ -1,423 +0,0 @@ -# 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 re -import sys -from typing import List - -# [START compute_instances_create_with_subnet] -# [START compute_instances_create_from_image_plus_snapshot_disk] -# [START compute_instances_create_from_snapshot] -# [START compute_instances_create_from_image_plus_empty_disk] -# [START compute_instances_create_from_custom_image] -# [START compute_instances_create_from_image] -from google.cloud import compute_v1 - - -# [END compute_instances_create_from_image] -# [END compute_instances_create_from_custom_image] -# [END compute_instances_create_from_image_plus_empty_disk] -# [END compute_instances_create_from_snapshot] -# [END compute_instances_create_from_image_plus_snapshot_disk] -# [END compute_instances_create_with_subnet] - - -# [START compute_instances_create_with_subnet] -# [START compute_instances_create_from_image_plus_snapshot_disk] -# [START compute_instances_create_from_image_plus_empty_disk] -# [START compute_instances_create_from_custom_image] -# [START compute_instances_create_from_image] -def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str -) -> 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}" - - 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 = True - boot_disk.boot = boot - return boot_disk - - -# [END compute_instances_create_from_image] -# [END compute_instances_create_from_custom_image] -# [END compute_instances_create_from_image_plus_empty_disk] -# [END compute_instances_create_from_image_plus_snapshot_disk] -# [END compute_instances_create_with_subnet] - - -# [START compute_instances_create_from_image_plus_empty_disk] -def empty_disk(disk_type: str, disk_size_gb: int) -> 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 - - 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 = True - disk.boot = False - return disk - - -# [END compute_instances_create_from_image_plus_empty_disk] - - -# [START compute_instances_create_from_image_plus_snapshot_disk] -# [START compute_instances_create_from_snapshot] -def disk_from_snapshot( - disk_type: str, disk_size_gb: int, boot: bool, disk_snapshot: str -) -> 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 - disk_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}" - - Returns: - AttachedDisk object configured to be created using the specified snapshot. - """ - disk = compute_v1.AttachedDisk() - initialize_params = compute_v1.AttachedDiskInitializeParams() - initialize_params.source_snapshot = disk_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 = True - disk.boot = boot - return disk - - -# [END compute_instances_create_from_snapshot] -# [END compute_instances_create_from_image_plus_snapshot_disk] - - -# [START compute_instances_create_with_subnet] -# [START compute_instances_create_from_image_plus_snapshot_disk] -# [START compute_instances_create_from_snapshot] -# [START compute_instances_create_from_image_plus_empty_disk] -# [START compute_instances_create_from_custom_image] -# [START compute_instances_create_from_image] -def create_with_disks( - 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, -) -> 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. - 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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. - """ - instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() - - # 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 - - # Collect information into the Instance object. - instance = compute_v1.Instance() - 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}" - instance.network_interfaces = [network_interface] - - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = 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_unary(request=request) - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) - print(f"Instance {instance_name} created.") - return instance - - -# [END compute_instances_create_from_image] -# [END compute_instances_create_from_custom_image] -# [END compute_instances_create_from_image_plus_empty_disk] -# [END compute_instances_create_from_snapshot] -# [END compute_instances_create_from_image_plus_snapshot_disk] -# [END compute_instances_create_with_subnet] - - -# [START compute_instances_create_from_image] -def create_from_public_image(project_id: str, zone: str, instance_name: str): - """ - 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. - """ - image_client = compute_v1.ImagesClient() - # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details - newest_debian = image_client.get_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_with_disks(project_id, zone, instance_name, disks) - return instance - - -# [END compute_instances_create_from_image] - - -# [START compute_instances_create_from_custom_image] -def create_from_custom_image( - project_id: str, zone: str, instance_name: str, custom_image_link: str -): - """ - 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)] - instance = create_with_disks(project_id, zone, instance_name, disks) - return instance - - -# [END compute_instances_create_from_custom_image] - - -# [START compute_instances_create_from_image_plus_empty_disk] -def create_with_additional_disk(project_id: str, zone: str, instance_name: str): - """ - Create a new VM instance with Debian 10 operating system and a 11 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. - """ - image_client = compute_v1.ImagesClient() - # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details - newest_debian = image_client.get_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), - empty_disk(disk_type, 11), - ] - instance = create_with_disks(project_id, zone, instance_name, disks) - return instance - - -# [END compute_instances_create_from_image_plus_empty_disk] - - -# [START compute_instances_create_from_snapshot] -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. - - 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, 11, True, snapshot_link)] - instance = create_with_disks(project_id, zone, instance_name, disks) - return instance - - -# [END compute_instances_create_from_snapshot] - - -# [START compute_instances_create_from_image_plus_snapshot_disk] -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. - """ - image_client = compute_v1.ImagesClient() - # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details - newest_debian = image_client.get_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_with_disks(project_id, zone, instance_name, disks) - return instance - - -# [END compute_instances_create_from_image_plus_snapshot_disk] - - -# [START compute_instances_create_with_subnet] -def create_with_subnet( - project_id: str, zone: str, instance_name: str, network_link: str, subnet_link: str -): - """ - 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. - """ - image_client = compute_v1.ImagesClient() - # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details - newest_debian = image_client.get_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_with_disks( - 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/compute/snippets/sample_custom_hostname.py b/compute/compute/snippets/sample_custom_hostname.py deleted file mode 100644 index 8d732f59490..00000000000 --- a/compute/compute/snippets/sample_custom_hostname.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# [START compute_instances_create_custom_hostname] -import sys - - -# [START compute_instances_get_hostname] -from google.cloud import compute_v1 - -# [END compute_instances_get_hostname] -# [END compute_instances_create_custom_hostname] - - -# [START compute_instances_create_custom_hostname] -def create_instance( - project_id: str, zone: str, instance_name: str, hostname: str, -) -> 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 you want to use. For example: “us-west3-b” - instance_name: name of the new virtual machine. - hostname: Custom hostname of the new VM instance. - Custom hostnames must conform to RFC 1035 requirements for valid hostnames. - - Returns: - Instance object. - """ - instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() - - # Describe 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-10" - ) - initialize_params.disk_size_gb = 10 - disk.initialize_params = initialize_params - disk.auto_delete = True - disk.boot = True - disk.type_ = "PERSISTENT" - - # Use the default VPC network. - network_interface = compute_v1.NetworkInterface() - network_interface.name = "default" - - # Collect information into the Instance object. - instance = compute_v1.Instance() - instance.name = instance_name - instance.disks = [disk] - instance.machine_type = f"zones/{zone}/machineTypes/e2-small" - instance.network_interfaces = [network_interface] - - # Custom hostnames are not resolved by the automatically created records - # provided by Compute Engine internal DNS. - # You must manually configure the DNS record for your custom hostname. - instance.hostname = hostname - - # 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_unary(request=request) - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) - print(f"Instance {instance_name} created.") - return instance - - -# [END compute_instances_create_custom_hostname] - - -# [START compute_instances_get_hostname] -def get_instance_hostname(project_id: str, zone: str, instance_name: str) -> str: - """ - Get the hostname set for 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 you want to check. - - Returns: - The hostname of given 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/compute/snippets/sample_delete_protection.py b/compute/compute/snippets/sample_delete_protection.py deleted file mode 100644 index 1894d3903bf..00000000000 --- a/compute/compute/snippets/sample_delete_protection.py +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# [START compute_delete_protection_create] -import sys - -# [END compute_delete_protection_create] - -# [START compute_delete_protection_get] -# [START compute_delete_protection_set] -# [START compute_delete_protection_create] -from google.cloud import compute_v1 - -# [END compute_delete_protection_create] -# [END compute_delete_protection_set] -# [END compute_delete_protection_get] - - -# [START compute_delete_protection_create] -def create_instance( - project_id: str, zone: str, instance_name: str, delete_protection: bool, -) -> 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 you want to use. For example: “us-west3-b” - instance_name: name of the new virtual machine. - 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() - operation_client = compute_v1.ZoneOperationsClient() - - # Describe 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-10" - ) - initialize_params.disk_size_gb = 10 - disk.initialize_params = initialize_params - disk.auto_delete = True - disk.boot = True - disk.type_ = "PERSISTENT" - - # Use the default VPC network. - network_interface = compute_v1.NetworkInterface() - network_interface.name = "default" - - # Collect information into the Instance object. - instance = compute_v1.Instance() - instance.name = instance_name - instance.disks = [disk] - instance.machine_type = f"zones/{zone}/machineTypes/e2-small" - instance.network_interfaces = [network_interface] - - # Set the delete protection bit - instance.deletion_protection = delete_protection - - # 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_unary(request=request) - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) - print(f"Instance {instance_name} created.") - return instance - - -# [END compute_delete_protection_create] - - -# [START compute_delete_protection_set] -def set_delete_protection( - project_id: str, zone: str, instance_name: str, delete_protection: bool -): - """ - 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 virtual machine to update. - delete_protection: boolean value indicating if the virtual machine should be - protected against deletion or not. - """ - instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() - - 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_unary(request) - operation_client.wait(project=project_id, zone=zone, operation=operation.name) - - -# [END compute_delete_protection_set] - - -# [START compute_delete_protection_get] -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 state 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/compute/snippets/sample_preemptible.py b/compute/compute/snippets/sample_preemptible.py deleted file mode 100644 index 3e1b2fc9714..00000000000 --- a/compute/compute/snippets/sample_preemptible.py +++ /dev/null @@ -1,193 +0,0 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# [START compute_preemptible_history] -import datetime - -# [END compute_preemptible_history] -# [START compute_preemptible_create] -import sys - -# [END compute_preemptible_create] - -# [START compute_preemptible_history] -from typing import List, Tuple - -# [END compute_preemptible_history] - -# [START compute_preemptible_create] -# [START compute_preemptible_check] -# [START compute_preemptible_history] -from google.cloud import compute_v1 - -# [END compute_preemptible_history] -# [END compute_preemptible_check] -# [END compute_preemptible_create] - -# [START compute_preemptible_history] -from google.cloud.compute_v1.services.zone_operations import pagers - -# [END compute_preemptible_history] - - -# [START compute_preemptible_create] -def create_preemptible_instance( - project_id: str, zone: str, instance_name: str, -) -> 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 you want to use. For example: "us-west3-b" - instance_name: name of the new virtual machine. - Returns: - Instance object. - """ - instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() - - # Describe 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-10" - ) - initialize_params.disk_size_gb = 10 - disk.initialize_params = initialize_params - disk.auto_delete = True - disk.boot = True - disk.type_ = "PERSISTENT" - - # Use the default VPC network. - network_interface = compute_v1.NetworkInterface() - network_interface.name = "default" - - # Collect information into the Instance object. - instance = compute_v1.Instance() - instance.name = instance_name - instance.disks = [disk] - instance.machine_type = f"zones/{zone}/machineTypes/e2-small" - instance.network_interfaces = [network_interface] - - # Set the preemptible setting - instance.scheduling = compute_v1.Scheduling() - instance.scheduling.preemptible = 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_unary(request=request) - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) - print(f"Instance {instance_name} created.") - return instance_client.get(project=project_id, zone=zone, instance=instance_name) - - -# [END compute_preemptible_create] - - -# [START compute_preemptible_check] -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] - - -# [START compute_preemptible_history] -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" - instance_name: name of the virtual machine to look for. - 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/compute/snippets/sample_start_stop.py b/compute/compute/snippets/sample_start_stop.py deleted file mode 100644 index 36fe54b9995..00000000000 --- a/compute/compute/snippets/sample_start_stop.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python - -# 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. - -""" -A sample script showing how to start and stop Google Compute Engine instances. -""" - -# [START compute_start_instance] -# [START compute_start_enc_instance] -# [START compute_stop_instance] -# [START compute_reset_instance] -from google.cloud import compute_v1 - -# [END compute_reset_instance] -# [END compute_stop_instance] -# [END compute_start_enc_instance] -# [END compute_start_instance] - - -# [START compute_start_instance] -def start_instance(project_id: str, zone: str, instance_name: str): - """ - 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 you want to start. - """ - instance_client = compute_v1.InstancesClient() - op_client = compute_v1.ZoneOperationsClient() - - op = instance_client.start_unary( - project=project_id, zone=zone, instance=instance_name - ) - - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - return - - -# [END compute_start_instance] - - -# [START compute_start_enc_instance] -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() - op_client = compute_v1.ZoneOperationsClient() - - 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] - - op = instance_client.start_with_encryption_key_unary( - project=project_id, - zone=zone, - instance=instance_name, - instances_start_with_encryption_key_request_resource=enc_data, - ) - - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - return - - -# [END compute_start_enc_instance] - - -# [START compute_stop_instance] -def stop_instance(project_id: str, zone: str, instance_name: str): - """ - 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() - op_client = compute_v1.ZoneOperationsClient() - - op = instance_client.stop_unary( - project=project_id, zone=zone, instance=instance_name - ) - - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - return - - -# [END compute_stop_instance] - - -# [START compute_reset_instance] -def reset_instance(project_id: str, zone: str, instance_name: str): - """ - Resets a running 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() - op_client = compute_v1.ZoneOperationsClient() - - op = instance_client.reset_unary( - project=project_id, zone=zone, instance=instance_name - ) - - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - return - - -# [END compute_reset_instance] diff --git a/compute/compute/snippets/sample_templates.py b/compute/compute/snippets/sample_templates.py deleted file mode 100644 index ddea063770a..00000000000 --- a/compute/compute/snippets/sample_templates.py +++ /dev/null @@ -1,251 +0,0 @@ -# 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. - -# [START compute_template_list ] -from typing import Iterable - -# [END compute_template_list ] - -# [START compute_template_create ] -# [START compute_template_list ] -# [START compute_template_get ] -# [START compute_template_create_from_instance ] -# [START compute_template_create_with_subnet ] -# [START compute_template_delete ] -from google.cloud import compute_v1 - -# [END compute_template_delete ] -# [END compute_template_create_with_subnet ] -# [END compute_template_create_from_instance ] -# [END compute_template_get ] -# [END compute_template_list ] -# [END compute_template_create ] - - -# [START compute_template_get ] -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 ] - - -# [START compute_template_list ] -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 ] - - -# [START compute_template_create ] -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_client = compute_v1.GlobalOperationsClient() - op = template_client.insert_unary( - project=project_id, instance_template_resource=template - ) - operation_client.wait(project=project_id, operation=op.name) - - return template_client.get(project=project_id, instance_template=template_name) - - -# [END compute_template_create ] - - -# [START compute_template_create_from_instance ] -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_client = compute_v1.GlobalOperationsClient() - op = template_client.insert_unary( - project=project_id, instance_template_resource=template - ) - operation_client.wait(project=project_id, operation=op.name) - - return template_client.get(project=project_id, instance_template=template_name) - - -# [END compute_template_create_from_instance ] - - -# [START compute_template_create_with_subnet ] -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_client = compute_v1.GlobalOperationsClient() - op = template_client.insert_unary( - project=project_id, instance_template_resource=template - ) - operation_client.wait(project=project_id, operation=op.name) - - return template_client.get(project=project_id, instance_template=template_name) - - -# [END compute_template_create_with_subnet ] - - -# [START compute_template_delete ] -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_client = compute_v1.GlobalOperationsClient() - op = template_client.delete_unary( - project=project_id, instance_template=template_name - ) - operation_client.wait(project=project_id, operation=op.name) - return - - -# [END compute_template_delete ] diff --git a/compute/compute/snippets/test_quickstart.py b/compute/compute/snippets/test_quickstart.py deleted file mode 100644 index 7057076568a..00000000000 --- a/compute/compute/snippets/test_quickstart.py +++ /dev/null @@ -1,36 +0,0 @@ -# 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 re -import typing -import uuid - -import google.auth - -from quickstart import main - -PROJECT = google.auth.default()[1] -INSTANCE_NAME = "i" + uuid.uuid4().hex[:10] -INSTANCE_ZONE = "europe-central2-b" - - -def test_main(capsys: typing.Any) -> None: - main(PROJECT, INSTANCE_ZONE, INSTANCE_NAME) - - out, _ = capsys.readouterr() - - assert f"Instance {INSTANCE_NAME} created." in out - assert re.search(f"Instances found in {INSTANCE_ZONE}:.+{INSTANCE_NAME}", out) - assert re.search(f"zones/{INSTANCE_ZONE}:.+{INSTANCE_NAME}", out) - assert f"Instance {INSTANCE_NAME} deleted." in out diff --git a/compute/compute/snippets/tests/__init__.py b/compute/compute/snippets/tests/__init__.py new file mode 100644 index 00000000000..4bbe0ffdb06 --- /dev/null +++ b/compute/compute/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/compute/snippets/test_sample_custom_hostname.py b/compute/compute/snippets/tests/test_custom_hostnames.py similarity index 77% rename from compute/compute/snippets/test_sample_custom_hostname.py rename to compute/compute/snippets/tests/test_custom_hostnames.py index 80666c45937..b8583db39bb 100644 --- a/compute/compute/snippets/test_sample_custom_hostname.py +++ b/compute/compute/snippets/tests/test_custom_hostnames.py @@ -17,9 +17,9 @@ import google.auth import pytest -from quickstart import delete_instance -from sample_custom_hostname import create_instance -from sample_custom_hostname import get_instance_hostname +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" @@ -39,13 +39,13 @@ def random_hostname(): yield "instance.{}.hostname".format(random.randint(0, 2 ** 10)) -def test_delete_protection(autodelete_instance_name, random_hostname): - instance = create_instance( +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_instance_hostname(PROJECT, INSTANCE_ZONE, autodelete_instance_name) + get_hostname(PROJECT, INSTANCE_ZONE, autodelete_instance_name) == random_hostname ) diff --git a/compute/compute/snippets/test_sample_delete_protection.py b/compute/compute/snippets/tests/test_delete_protection.py similarity index 80% rename from compute/compute/snippets/test_sample_delete_protection.py rename to compute/compute/snippets/tests/test_delete_protection.py index ee57a3e22fd..643c9294d15 100644 --- a/compute/compute/snippets/test_sample_delete_protection.py +++ b/compute/compute/snippets/tests/test_delete_protection.py @@ -16,10 +16,10 @@ import google.auth import pytest -from quickstart import delete_instance -from sample_delete_protection import create_instance -from sample_delete_protection import get_delete_protection -from sample_delete_protection import set_delete_protection +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" @@ -38,7 +38,9 @@ def autodelete_instance_name(): def test_delete_protection(autodelete_instance_name): - instance = create_instance(PROJECT, INSTANCE_ZONE, autodelete_instance_name, True) + instance = create_protected_instance( + PROJECT, INSTANCE_ZONE, autodelete_instance_name + ) assert instance.name == autodelete_instance_name assert ( diff --git a/compute/compute/snippets/test_sample_preemptible.py b/compute/compute/snippets/tests/test_preemptible.py similarity index 85% rename from compute/compute/snippets/test_sample_preemptible.py rename to compute/compute/snippets/tests/test_preemptible.py index 047a721e370..06c5092799c 100644 --- a/compute/compute/snippets/test_sample_preemptible.py +++ b/compute/compute/snippets/tests/test_preemptible.py @@ -16,10 +16,10 @@ import google.auth import pytest -from quickstart import delete_instance -from sample_preemptible import create_preemptible_instance -from sample_preemptible import is_preemptible -from sample_preemptible import list_zone_operations +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" diff --git a/compute/compute/snippets/test_sample_create_vm.py b/compute/compute/snippets/tests/test_sample_create_vm.py similarity index 72% rename from compute/compute/snippets/test_sample_create_vm.py rename to compute/compute/snippets/tests/test_sample_create_vm.py index 3e3189d904a..b08617f6e24 100644 --- a/compute/compute/snippets/test_sample_create_vm.py +++ b/compute/compute/snippets/tests/test_sample_create_vm.py @@ -1,32 +1,38 @@ -# Copyright 2021 Google LLC +# Copyright 2022 Google LLC # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 quickstart import delete_instance, wait_for_operation - -from sample_create_vm import ( +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, - create_from_snapshot, +) +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_snapshotted_data_disk import ( create_with_snapshotted_data_disk, - create_with_subnet, ) +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" @@ -39,8 +45,8 @@ def get_active_debian(): return image_client.get_from_family(project="debian-cloud", family="debian-11") -@pytest.fixture(scope="class") -def src_disk(request): +@pytest.fixture() +def src_disk(): disk_client = compute_v1.DisksClient() disk = compute_v1.Disk() @@ -53,7 +59,6 @@ def src_disk(request): wait_for_operation(op, PROJECT) try: disk = disk_client.get(project=PROJECT, zone=INSTANCE_ZONE, disk=disk.name) - request.cls.disk = disk yield disk finally: op = disk_client.delete_unary( @@ -62,8 +67,8 @@ def src_disk(request): wait_for_operation(op, PROJECT) -@pytest.fixture(scope="class") -def snapshot(request, src_disk): +@pytest.fixture() +def snapshot(src_disk): snapshot_client = compute_v1.SnapshotsClient() snapshot = compute_v1.Snapshot() snapshot.name = "test-snap-" + uuid.uuid4().hex[:10] @@ -76,10 +81,9 @@ def snapshot(request, src_disk): ) wait_for_operation(op, PROJECT) try: - request.cls.snapshot = snapshot_client.get( + snapshot = snapshot_client.get( project=PROJECT, snapshot=snapshot.name ) - snapshot = request.cls.snapshot yield snapshot finally: @@ -87,8 +91,8 @@ def snapshot(request, src_disk): wait_for_operation(op, PROJECT) -@pytest.fixture(scope="class") -def image(request, src_disk): +@pytest.fixture() +def image(src_disk): image_client = compute_v1.ImagesClient() image = compute_v1.Image() image.source_disk = src_disk.self_link @@ -98,45 +102,47 @@ def image(request, src_disk): wait_for_operation(op, PROJECT) try: image = image_client.get(project=PROJECT, image=image.name) - request.cls.image = image yield image finally: op = image_client.delete_unary(project=PROJECT, image=image.name) wait_for_operation(op, PROJECT) -@pytest.mark.usefixtures("image", "snapshot") class TestCreation: - def test_create_from_custom_image(self): + def test_create_from_custom_image(self, image): instance_name = "i" + uuid.uuid4().hex[:10] instance = create_from_custom_image( - PROJECT, INSTANCE_ZONE, instance_name, self.image.self_link + PROJECT, INSTANCE_ZONE, instance_name, image.self_link ) try: assert ( - instance.disks[0].initialize_params.source_image == self.image.self_link + instance.disks[0].initialize_params.source_image == image.self_link ) finally: delete_instance(PROJECT, INSTANCE_ZONE, instance_name) def test_create_from_public_image(self): instance_name = "i" + uuid.uuid4().hex[:10] - instance = create_from_public_image(PROJECT, INSTANCE_ZONE, instance_name,) + instance = create_from_public_image( + PROJECT, + INSTANCE_ZONE, + instance_name, + ) try: assert "debian-cloud" in instance.disks[0].initialize_params.source_image assert "debian-10" in instance.disks[0].initialize_params.source_image finally: delete_instance(PROJECT, INSTANCE_ZONE, instance_name) - def test_create_from_snapshot(self): + def test_create_from_snapshot(self, snapshot): instance_name = "i" + uuid.uuid4().hex[:10] instance = create_from_snapshot( - PROJECT, INSTANCE_ZONE, instance_name, self.snapshot.self_link + PROJECT, INSTANCE_ZONE, instance_name, snapshot.self_link ) try: assert ( instance.disks[0].initialize_params.source_snapshot - == self.snapshot.self_link + == snapshot.self_link ) finally: delete_instance(PROJECT, INSTANCE_ZONE, instance_name) @@ -155,10 +161,10 @@ def test_create_with_additional_disk(self): finally: delete_instance(PROJECT, INSTANCE_ZONE, instance_name) - def test_create_with_snapshotted_data_disk(self): + def test_create_with_snapshotted_data_disk(self, snapshot): instance_name = "i" + uuid.uuid4().hex[:10] instance = create_with_snapshotted_data_disk( - PROJECT, INSTANCE_ZONE, instance_name, self.snapshot.self_link + PROJECT, INSTANCE_ZONE, instance_name, snapshot.self_link ) try: assert any( diff --git a/compute/compute/snippets/test_sample_custom_types.py b/compute/compute/snippets/tests/test_sample_custom_types.py similarity index 84% rename from compute/compute/snippets/test_sample_custom_types.py rename to compute/compute/snippets/tests/test_sample_custom_types.py index 812b04b5083..4b7c8108cb2 100644 --- a/compute/compute/snippets/test_sample_custom_types.py +++ b/compute/compute/snippets/tests/test_sample_custom_types.py @@ -16,13 +16,18 @@ import google.auth import pytest -from quickstart import create_instance, delete_instance -from sample_custom_types import ( - add_extended_memory_to_instance, - create_custom_instance, +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, - CustomMachineType, ) +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" @@ -39,14 +44,22 @@ def auto_delete_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, "n2-custom-8-10240" + 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, @@ -63,6 +76,11 @@ def test_custom_instance_creation(auto_delete_instance_name): 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, diff --git a/compute/compute/snippets/test_sample_default_values.py b/compute/compute/snippets/tests/test_sample_default_values.py similarity index 93% rename from compute/compute/snippets/test_sample_default_values.py rename to compute/compute/snippets/tests/test_sample_default_values.py index 23182e0777b..f609b3dd522 100644 --- a/compute/compute/snippets/test_sample_default_values.py +++ b/compute/compute/snippets/tests/test_sample_default_values.py @@ -20,11 +20,9 @@ import google.cloud.storage as storage import pytest -from sample_default_values import ( - disable_usage_export, - get_usage_export_bucket, - set_usage_export_bucket, -) +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] diff --git a/compute/compute/snippets/test_sample_firewall.py b/compute/compute/snippets/tests/test_sample_firewall.py similarity index 94% rename from compute/compute/snippets/test_sample_firewall.py rename to compute/compute/snippets/tests/test_sample_firewall.py index 51717439528..86f97867585 100644 --- a/compute/compute/snippets/test_sample_firewall.py +++ b/compute/compute/snippets/tests/test_sample_firewall.py @@ -19,13 +19,10 @@ from google.cloud import compute_v1 import pytest - -from sample_firewall import ( - create_firewall_rule, - delete_firewall_rule, - get_firewall_rule, - patch_firewall_priority, -) +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] diff --git a/compute/compute/snippets/test_sample_images.py b/compute/compute/snippets/tests/test_sample_images.py similarity index 93% rename from compute/compute/snippets/test_sample_images.py rename to compute/compute/snippets/tests/test_sample_images.py index 23346c1f885..18852ac09a0 100644 --- a/compute/compute/snippets/test_sample_images.py +++ b/compute/compute/snippets/tests/test_sample_images.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from sample_images import get_image, list_images +from ..images.get import get_image +from ..images.list import list_images def test_list_images(): diff --git a/compute/compute/snippets/test_sample_instance_from_template.py b/compute/compute/snippets/tests/test_sample_instance_from_template.py similarity index 94% rename from compute/compute/snippets/test_sample_instance_from_template.py rename to compute/compute/snippets/tests/test_sample_instance_from_template.py index c6a2f0f62e6..e322082581b 100644 --- a/compute/compute/snippets/test_sample_instance_from_template.py +++ b/compute/compute/snippets/tests/test_sample_instance_from_template.py @@ -17,13 +17,14 @@ from google.cloud import compute_v1 import pytest -from quickstart import delete_instance -from sample_instance_from_template import ( +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" diff --git a/compute/compute/snippets/test_sample_pagination.py b/compute/compute/snippets/tests/test_sample_pagination.py similarity index 66% rename from compute/compute/snippets/test_sample_pagination.py rename to compute/compute/snippets/tests/test_sample_pagination.py index 77672ba50c3..41e06703d13 100644 --- a/compute/compute/snippets/test_sample_pagination.py +++ b/compute/compute/snippets/tests/test_sample_pagination.py @@ -11,20 +11,17 @@ # WITHOUT 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 typing - -from sample_pagination import print_images_list, print_images_list_by_page +from ..images.pagination import print_images_list +from ..images.pagination import print_images_list_by_page PROJECT = "windows-sql-cloud" -def test_pagination(capsys: typing.Any) -> None: - print_images_list(PROJECT) - out, _ = capsys.readouterr() +def test_pagination() -> None: + out = print_images_list(PROJECT) assert len(out.splitlines()) > 2 -def test_pagination_page(capsys: typing.Any) -> None: - print_images_list_by_page(PROJECT, 2) - out, _ = capsys.readouterr() +def test_pagination_page() -> None: + out = print_images_list_by_page(PROJECT, 2) assert "Page 2" in out diff --git a/compute/compute/snippets/test_sample_start_stop.py b/compute/compute/snippets/tests/test_sample_start_stop.py similarity index 97% rename from compute/compute/snippets/test_sample_start_stop.py rename to compute/compute/snippets/tests/test_sample_start_stop.py index bcf249f22dd..737400f8a64 100644 --- a/compute/compute/snippets/test_sample_start_stop.py +++ b/compute/compute/snippets/tests/test_sample_start_stop.py @@ -19,14 +19,11 @@ import google.auth from google.cloud import compute_v1 - import pytest -from sample_start_stop import ( - start_instance, - start_instance_with_encryption_key, - stop_instance, -) +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] diff --git a/compute/compute/snippets/test_sample_templates.py b/compute/compute/snippets/tests/test_sample_templates.py similarity index 88% rename from compute/compute/snippets/test_sample_templates.py rename to compute/compute/snippets/tests/test_sample_templates.py index 2c60aaafb04..624a6f0178a 100644 --- a/compute/compute/snippets/test_sample_templates.py +++ b/compute/compute/snippets/tests/test_sample_templates.py @@ -17,17 +17,16 @@ import google.auth import pytest -from sample_templates import ( - create_template, - create_template_from_instance, - create_template_with_subnet, - delete_instance_template, - list_instance_templates, -) - # Turning off F401 check because flake8 doesn't recognize using # PyTest fixture as parameter as usage. -from test_sample_start_stop import compute_instance # noqa: F401 +from .test_sample_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] diff --git a/compute/compute/snippets/usage_report/__init__.py b/compute/compute/snippets/usage_report/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/snippets/sample_default_values.py b/compute/compute/snippets/usage_report/usage_reports.py similarity index 81% rename from compute/compute/snippets/sample_default_values.py rename to compute/compute/snippets/usage_report/usage_reports.py index 35148795279..54af6034bca 100644 --- a/compute/compute/snippets/sample_default_values.py +++ b/compute/compute/snippets/usage_report/usage_reports.py @@ -1,28 +1,34 @@ -#!/usr/bin/env python - -# Copyright 2021 Google LLC +# Copyright 2022 Google LLC # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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. """ + +# 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] from google.cloud import compute_v1 + # [END compute_usage_report_disable] # [END compute_usage_report_get] # [END compute_usage_report_set] @@ -44,9 +50,9 @@ def set_usage_export_bucket( 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( - bucket_name=bucket_name, report_name_prefix=report_name_prefix - ) + 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 @@ -70,7 +76,6 @@ def set_usage_export_bucket( # [END compute_usage_report_set] - # [START compute_usage_report_get] def get_usage_export_bucket(project_id: str) -> compute_v1.UsageExportLocation: """ diff --git a/compute/compute/test_sgs.py b/compute/compute/test_sgs.py index f1c1d71ae14..dcc030a17f2 100644 --- a/compute/compute/test_sgs.py +++ b/compute/compute/test_sgs.py @@ -16,7 +16,7 @@ from pathlib import Path import tempfile -import sgs +from . import sgs FIXTURE_INGREDIENTS = Path("sgs_test_fixtures/ingredients") FIXTURE_RECIPES = Path("sgs_test_fixtures/recipes") @@ -26,7 +26,7 @@ def test_sgs_generate(): with tempfile.TemporaryDirectory() as tmp_dir: args = Namespace(output_dir=tmp_dir) - sgs.generate(args, FIXTURE_INGREDIENTS, FIXTURE_RECIPES) + 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() From 48b01d9dd657b7c6482e705719982cd1e80bb137 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Wed, 9 Mar 2022 00:21:03 +0100 Subject: [PATCH 066/113] chore(deps): update dependency google-cloud-compute to v1.1.0 (#237) --- compute/compute/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index 23ea27c39c0..c0be0f7c63c 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 black==22.1.0 -google-cloud-compute==1.0.0 \ No newline at end of file +google-cloud-compute==1.1.0 \ No newline at end of file From 4476aedf148beb3c434a78b70e454c992545f7bd Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Sun, 13 Mar 2022 17:08:13 +0100 Subject: [PATCH 067/113] chore(deps): update dependency pytest to v7.1.0 (#238) --- compute/compute/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements-test.txt b/compute/compute/requirements-test.txt index 45720ccd4e4..fd74ccfa7cc 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -1,4 +1,4 @@ -pytest==7.0.1 +pytest==7.1.0 pytest-parallel==0.1.1 flaky==3.7.0 google-cloud-storage==2.1.0; python_version == '3.6' From b351db5400e102854326220cf9b68dfa313a1a3d Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 14 Mar 2022 19:29:28 +0100 Subject: [PATCH 068/113] chore(deps): update dependency google-cloud-storage to v2.2.0 (#239) --- compute/compute/requirements-test.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compute/compute/requirements-test.txt b/compute/compute/requirements-test.txt index fd74ccfa7cc..4ee0022ff8b 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -1,5 +1,5 @@ pytest==7.1.0 pytest-parallel==0.1.1 flaky==3.7.0 -google-cloud-storage==2.1.0; python_version == '3.6' -google-cloud-storage==2.1.0; python_version >= '3.7' \ No newline at end of file +google-cloud-storage==2.2.0; python_version == '3.6' +google-cloud-storage==2.2.0; python_version >= '3.7' \ No newline at end of file From c02f703d4e0e89b7f79fe9683d7464682faa3c6b Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Wed, 16 Mar 2022 12:34:29 +0100 Subject: [PATCH 069/113] chore(deps): update dependency google-cloud-storage to v2.2.1 (#240) * chore(deps): update dependency google-cloud-storage to v2.2.1 * chore: remove pin for python3.6 samples Co-authored-by: Anthonios Partheniou --- compute/compute/requirements-test.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compute/compute/requirements-test.txt b/compute/compute/requirements-test.txt index 4ee0022ff8b..0538df0bb54 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -1,5 +1,4 @@ pytest==7.1.0 pytest-parallel==0.1.1 flaky==3.7.0 -google-cloud-storage==2.2.0; python_version == '3.6' -google-cloud-storage==2.2.0; python_version >= '3.7' \ No newline at end of file +google-cloud-storage==2.2.1 From 82f7b7e12244eb912615e4b894153be13f0de7fa Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Sat, 19 Mar 2022 11:36:29 +0100 Subject: [PATCH 070/113] chore(deps): update dependency pytest to v7.1.1 (#241) --- compute/compute/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements-test.txt b/compute/compute/requirements-test.txt index 0538df0bb54..0d1f5b210dd 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -1,4 +1,4 @@ -pytest==7.1.0 +pytest==7.1.1 pytest-parallel==0.1.1 flaky==3.7.0 google-cloud-storage==2.2.1 From 5638466553e0802fc028a4bd5555c6bf04102319 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 23 Mar 2022 16:03:25 +0100 Subject: [PATCH 071/113] chore(samples): Making samples required for moving VM instance docs (#242) Co-authored-by: Owl Bot --- .../ingredients/disks/autodelete_change.py | 61 ++++++++++ .../ingredients/disks/create_from_image.py | 67 +++++++++++ .../ingredients/disks/create_from_snapshot.py | 65 +++++++++++ compute/compute/ingredients/disks/delete.py | 48 ++++++++ compute/compute/ingredients/disks/list.py | 43 +++++++ .../ingredients/instances/create_instance.py | 29 +++++ .../compute/ingredients/snapshots/create.py | 60 ++++++++++ .../compute/ingredients/snapshots/delete.py | 49 ++++++++ compute/compute/ingredients/snapshots/list.py | 45 ++++++++ compute/compute/noxfile_config.py | 2 +- compute/compute/recipes/disks/__init__.py | 13 +++ .../recipes/disks/autodelete_change.py | 21 ++++ .../recipes/disks/create_from_image.py | 21 ++++ .../recipes/disks/create_from_snapshot.py | 21 ++++ compute/compute/recipes/disks/delete.py | 21 ++++ compute/compute/recipes/disks/list.py | 22 ++++ compute/compute/recipes/snapshots/__init__.py | 13 +++ compute/compute/recipes/snapshots/create.py | 22 ++++ compute/compute/recipes/snapshots/delete.py | 21 ++++ compute/compute/recipes/snapshots/list.py | 21 ++++ compute/compute/sgs.py | 2 + compute/compute/snippets/disks/__init__.py | 13 +++ .../snippets/disks/autodelete_change.py | 78 +++++++++++++ .../snippets/disks/create_from_image.py | 81 +++++++++++++ .../snippets/disks/create_from_snapshot.py | 80 +++++++++++++ compute/compute/snippets/disks/delete.py | 55 +++++++++ compute/compute/snippets/disks/list.py | 49 ++++++++ compute/compute/snippets/firewall/create.py | 5 +- compute/compute/snippets/firewall/delete.py | 4 +- compute/compute/snippets/firewall/list.py | 2 +- compute/compute/snippets/firewall/main.py | 25 +++- compute/compute/snippets/firewall/patch.py | 4 +- compute/compute/snippets/images/get.py | 10 ++ compute/compute/snippets/images/pagination.py | 2 +- compute/compute/snippets/instances/create.py | 64 +++++++++-- .../create_from_custom_image.py | 64 +++++++++-- .../create_from_public_image.py | 64 +++++++++-- .../create_from_snapshot.py | 63 +++++++--- .../create_with_additional_disk.py | 78 ++++++++++--- .../create_with_snapshotted_data_disk.py | 77 ++++++++++--- .../snippets/instances/create_with_subnet.py | 64 +++++++++-- .../instances/custom_hostname/create.py | 64 +++++++++-- .../create_shared_with_helper.py | 84 +++++++++++--- .../create_with_helper.py | 84 +++++++++++--- .../create_without_helper.py | 108 +++++++++++------- .../extra_mem_no_helper.py | 68 ++++++++--- .../custom_machine_types/helper_class.py | 20 +++- .../custom_machine_types/update_memory.py | 11 ++ compute/compute/snippets/instances/delete.py | 5 + .../instances/delete_protection/create.py | 64 +++++++++-- .../instances/delete_protection/get.py | 2 +- .../instances/delete_protection/set.py | 4 +- .../compute/snippets/instances/list_all.py | 4 +- .../preemptible/create_preemptible.py | 64 +++++++++-- compute/compute/snippets/instances/reset.py | 7 +- compute/compute/snippets/instances/start.py | 7 +- .../snippets/instances/start_encrypted.py | 5 + compute/compute/snippets/instances/stop.py | 9 +- .../compute/snippets/snapshots/__init__.py | 13 +++ compute/compute/snippets/snapshots/create.py | 67 +++++++++++ compute/compute/snippets/snapshots/delete.py | 54 +++++++++ compute/compute/snippets/snapshots/list.py | 49 ++++++++ ..._sample_create_vm.py => test_create_vm.py} | 4 +- ...e_custom_types.py => test_custom_types.py} | 0 ...fault_values.py => test_default_values.py} | 0 compute/compute/snippets/tests/test_disks.py | 59 ++++++++++ ...st_sample_firewall.py => test_firewall.py} | 0 .../{test_sample_images.py => test_images.py} | 0 ...late.py => test_instance_from_template.py} | 0 ...rt_stop.py => test_instance_start_stop.py} | 0 ...ample_pagination.py => test_pagination.py} | 0 .../compute/snippets/tests/test_snapshots.py | 58 ++++++++++ ..._sample_templates.py => test_templates.py} | 2 +- .../snippets/usage_report/usage_reports.py | 2 +- 74 files changed, 2227 insertions(+), 240 deletions(-) create mode 100644 compute/compute/ingredients/disks/autodelete_change.py create mode 100644 compute/compute/ingredients/disks/create_from_image.py create mode 100644 compute/compute/ingredients/disks/create_from_snapshot.py create mode 100644 compute/compute/ingredients/disks/delete.py create mode 100644 compute/compute/ingredients/disks/list.py create mode 100644 compute/compute/ingredients/snapshots/create.py create mode 100644 compute/compute/ingredients/snapshots/delete.py create mode 100644 compute/compute/ingredients/snapshots/list.py create mode 100644 compute/compute/recipes/disks/__init__.py create mode 100644 compute/compute/recipes/disks/autodelete_change.py create mode 100644 compute/compute/recipes/disks/create_from_image.py create mode 100644 compute/compute/recipes/disks/create_from_snapshot.py create mode 100644 compute/compute/recipes/disks/delete.py create mode 100644 compute/compute/recipes/disks/list.py create mode 100644 compute/compute/recipes/snapshots/__init__.py create mode 100644 compute/compute/recipes/snapshots/create.py create mode 100644 compute/compute/recipes/snapshots/delete.py create mode 100644 compute/compute/recipes/snapshots/list.py create mode 100644 compute/compute/snippets/disks/__init__.py create mode 100644 compute/compute/snippets/disks/autodelete_change.py create mode 100644 compute/compute/snippets/disks/create_from_image.py create mode 100644 compute/compute/snippets/disks/create_from_snapshot.py create mode 100644 compute/compute/snippets/disks/delete.py create mode 100644 compute/compute/snippets/disks/list.py create mode 100644 compute/compute/snippets/snapshots/__init__.py create mode 100644 compute/compute/snippets/snapshots/create.py create mode 100644 compute/compute/snippets/snapshots/delete.py create mode 100644 compute/compute/snippets/snapshots/list.py rename compute/compute/snippets/tests/{test_sample_create_vm.py => test_create_vm.py} (98%) rename compute/compute/snippets/tests/{test_sample_custom_types.py => test_custom_types.py} (100%) rename compute/compute/snippets/tests/{test_sample_default_values.py => test_default_values.py} (100%) create mode 100644 compute/compute/snippets/tests/test_disks.py rename compute/compute/snippets/tests/{test_sample_firewall.py => test_firewall.py} (100%) rename compute/compute/snippets/tests/{test_sample_images.py => test_images.py} (100%) rename compute/compute/snippets/tests/{test_sample_instance_from_template.py => test_instance_from_template.py} (100%) rename compute/compute/snippets/tests/{test_sample_start_stop.py => test_instance_start_stop.py} (100%) rename compute/compute/snippets/tests/{test_sample_pagination.py => test_pagination.py} (100%) create mode 100644 compute/compute/snippets/tests/test_snapshots.py rename compute/compute/snippets/tests/{test_sample_templates.py => test_templates.py} (98%) diff --git a/compute/compute/ingredients/disks/autodelete_change.py b/compute/compute/ingredients/disks/autodelete_change.py new file mode 100644 index 00000000000..589fe75bf37 --- /dev/null +++ b/compute/compute/ingredients/disks/autodelete_change.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 +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_unary(project=project_id, zone=zone, instance=instance_name, instance_resource=instance) + operation_client = compute_v1.ZoneOperationsClient() + operation = operation_client.wait(project=project_id, zone=zone, operation=operation.name) + + if operation.error: + print("Error during instance update:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warnings during instance update:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + return +# diff --git a/compute/compute/ingredients/disks/create_from_image.py b/compute/compute/ingredients/disks/create_from_image.py new file mode 100644 index 00000000000..c8e1bdce148 --- /dev/null +++ b/compute/compute/ingredients/disks/create_from_image.py @@ -0,0 +1,67 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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_unary(project=project_id, zone=zone, disk_resource=disk) + operation_client = compute_v1.ZoneOperationsClient() + operation = operation_client.wait(project=project_id, zone=zone, operation=operation.name) + + if operation.error: + print("Error during disk creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warnings during disk creation:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return disk_client.get(project=project_id, zone=zone, disk=disk.name) +# diff --git a/compute/compute/ingredients/disks/create_from_snapshot.py b/compute/compute/ingredients/disks/create_from_snapshot.py new file mode 100644 index 00000000000..059ab01801d --- /dev/null +++ b/compute/compute/ingredients/disks/create_from_snapshot.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 +import sys + + +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_unary(project=project_id, zone=zone, disk_resource=disk) + operation_client = compute_v1.ZoneOperationsClient() + operation = operation_client.wait(project=project_id, zone=zone, operation=operation.name) + + if operation.error: + print("Error during disk creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + + if operation.warnings: + print("Warnings during disk creation:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return disk_client.get(project=project_id, zone=zone, disk=disk_name) +# diff --git a/compute/compute/ingredients/disks/delete.py b/compute/compute/ingredients/disks/delete.py new file mode 100644 index 00000000000..d66294eceb0 --- /dev/null +++ b/compute/compute/ingredients/disks/delete.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 +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_unary(project=project_id, zone=zone, disk=disk_name) + operation_client = compute_v1.ZoneOperationsClient() + operation = operation_client.wait(project=project_id, zone=zone, operation=operation.name) + + if operation.error: + print("Error during disk delete operation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warnings during disk delete operation:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + return +# diff --git a/compute/compute/ingredients/disks/list.py b/compute/compute/ingredients/disks/list.py new file mode 100644 index 00000000000..ec3ecab83cc --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/create_instance.py b/compute/compute/ingredients/instances/create_instance.py index 85c2e48185e..99fad23e414 100644 --- a/compute/compute/ingredients/instances/create_instance.py +++ b/compute/compute/ingredients/instances/create_instance.py @@ -33,6 +33,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -55,6 +59,16 @@ def create_instance( 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. custom_hostname: Custom hostname of the new VM instance. @@ -73,6 +87,18 @@ def create_instance( 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.name = instance_name @@ -82,6 +108,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: diff --git a/compute/compute/ingredients/snapshots/create.py b/compute/compute/ingredients/snapshots/create.py new file mode 100644 index 00000000000..3e3b2a97cfe --- /dev/null +++ b/compute/compute/ingredients/snapshots/create.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 +import sys + + +from google.cloud import compute_v1 + + +# +def create_snapshot(project_id: str, zone: str, disk_name: str, snapshot_name: str) -> compute_v1.Snapshot: + """ + Create a snapshot of a disk. + + 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 snapshot. + disk_name: name of the disk you want to snapshot. + snapshot_name: name of the snapshot to be created. + + Returns: + The new snapshot instance. + """ + disk_client = compute_v1.DisksClient() + disk = disk_client.get(project=project_id, zone=zone, disk=disk_name) + snapshot = compute_v1.Snapshot() + snapshot.source_disk = disk.self_link + snapshot.name = snapshot_name + + snapshot_client = compute_v1.SnapshotsClient() + operation = snapshot_client.insert_unary(project=project_id, snapshot_resource=snapshot) + op_client = compute_v1.GlobalOperationsClient() + operation = op_client.wait(project=project_id, operation=operation.name) + + if operation.error: + print("Error during snapshot creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warnings during snapshot creation:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return snapshot_client.get(project=project_id, snapshot=snapshot_name) + +# diff --git a/compute/compute/ingredients/snapshots/delete.py b/compute/compute/ingredients/snapshots/delete.py new file mode 100644 index 00000000000..dd3f1f29c15 --- /dev/null +++ b/compute/compute/ingredients/snapshots/delete.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 +import sys +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_unary(project=project_id, snapshot=snapshot_name) + op_client = compute_v1.GlobalOperationsClient() + operation = op_client.wait(project=project_id, operation=operation.name) + + if operation.error: + print("Error during snapshot deletion:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warnings during snapshot deletion:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return +# diff --git a/compute/compute/ingredients/snapshots/list.py b/compute/compute/ingredients/snapshots/list.py new file mode 100644 index 00000000000..87539f1738d --- /dev/null +++ b/compute/compute/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/compute/noxfile_config.py b/compute/compute/noxfile_config.py index 33170d346c8..13928a8af82 100644 --- a/compute/compute/noxfile_config.py +++ b/compute/compute/noxfile_config.py @@ -13,6 +13,6 @@ # limitations under the License. TEST_CONFIG_OVERRIDE = { - # Tests in test_sample_default_values.py require separate projects to not interfere with each other. + # 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/compute/recipes/disks/__init__.py b/compute/compute/recipes/disks/__init__.py new file mode 100644 index 00000000000..4bbe0ffdb06 --- /dev/null +++ b/compute/compute/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/compute/recipes/disks/autodelete_change.py b/compute/compute/recipes/disks/autodelete_change.py new file mode 100644 index 00000000000..33120f89775 --- /dev/null +++ b/compute/compute/recipes/disks/autodelete_change.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/compute/recipes/disks/create_from_image.py b/compute/compute/recipes/disks/create_from_image.py new file mode 100644 index 00000000000..407b4aa0d72 --- /dev/null +++ b/compute/compute/recipes/disks/create_from_image.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/compute/recipes/disks/create_from_snapshot.py b/compute/compute/recipes/disks/create_from_snapshot.py new file mode 100644 index 00000000000..c33060ca2cd --- /dev/null +++ b/compute/compute/recipes/disks/create_from_snapshot.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/compute/recipes/disks/delete.py b/compute/compute/recipes/disks/delete.py new file mode 100644 index 00000000000..e319a075c85 --- /dev/null +++ b/compute/compute/recipes/disks/delete.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/compute/recipes/disks/list.py b/compute/compute/recipes/disks/list.py new file mode 100644 index 00000000000..3b0e685faaf --- /dev/null +++ b/compute/compute/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/compute/recipes/snapshots/__init__.py b/compute/compute/recipes/snapshots/__init__.py new file mode 100644 index 00000000000..4bbe0ffdb06 --- /dev/null +++ b/compute/compute/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/compute/recipes/snapshots/create.py b/compute/compute/recipes/snapshots/create.py new file mode 100644 index 00000000000..b77eb7a9832 --- /dev/null +++ b/compute/compute/recipes/snapshots/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/compute/recipes/snapshots/delete.py b/compute/compute/recipes/snapshots/delete.py new file mode 100644 index 00000000000..ff8e89c8e07 --- /dev/null +++ b/compute/compute/recipes/snapshots/delete.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/compute/recipes/snapshots/list.py b/compute/compute/recipes/snapshots/list.py new file mode 100644 index 00000000000..34b5701da86 --- /dev/null +++ b/compute/compute/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/compute/sgs.py b/compute/compute/sgs.py index f8278f7b561..d8b2dbeb8d0 100644 --- a/compute/compute/sgs.py +++ b/compute/compute/sgs.py @@ -151,6 +151,8 @@ def load_ingredients(path: Path) -> dict: 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 diff --git a/compute/compute/snippets/disks/__init__.py b/compute/compute/snippets/disks/__init__.py new file mode 100644 index 00000000000..4bbe0ffdb06 --- /dev/null +++ b/compute/compute/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/compute/snippets/disks/autodelete_change.py b/compute/compute/snippets/disks/autodelete_change.py new file mode 100644 index 00000000000..943564cd181 --- /dev/null +++ b/compute/compute/snippets/disks/autodelete_change.py @@ -0,0 +1,78 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 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_unary( + project=project_id, + zone=zone, + instance=instance_name, + instance_resource=instance, + ) + operation_client = compute_v1.ZoneOperationsClient() + operation = operation_client.wait( + project=project_id, zone=zone, operation=operation.name + ) + + if operation.error: + print("Error during instance update:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warnings during instance update:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + return + + +# [END compute_disk_autodelete_change] diff --git a/compute/compute/snippets/disks/create_from_image.py b/compute/compute/snippets/disks/create_from_image.py new file mode 100644 index 00000000000..74e998ada27 --- /dev/null +++ b/compute/compute/snippets/disks/create_from_image.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. +# 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 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_unary( + project=project_id, zone=zone, disk_resource=disk + ) + operation_client = compute_v1.ZoneOperationsClient() + operation = operation_client.wait( + project=project_id, zone=zone, operation=operation.name + ) + + if operation.error: + print("Error during disk creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warnings during disk creation:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return disk_client.get(project=project_id, zone=zone, disk=disk.name) + + +# [END compute_disk_create_from_image] diff --git a/compute/compute/snippets/disks/create_from_snapshot.py b/compute/compute/snippets/disks/create_from_snapshot.py new file mode 100644 index 00000000000..2421adb0f8e --- /dev/null +++ b/compute/compute/snippets/disks/create_from_snapshot.py @@ -0,0 +1,80 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 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_unary( + project=project_id, zone=zone, disk_resource=disk + ) + operation_client = compute_v1.ZoneOperationsClient() + operation = operation_client.wait( + project=project_id, zone=zone, operation=operation.name + ) + + if operation.error: + print("Error during disk creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + + if operation.warnings: + print("Warnings during disk creation:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return disk_client.get(project=project_id, zone=zone, disk=disk_name) + + +# [END compute_disk_create_from_snapshot] diff --git a/compute/compute/snippets/disks/delete.py b/compute/compute/snippets/disks/delete.py new file mode 100644 index 00000000000..0b5ea3cb39a --- /dev/null +++ b/compute/compute/snippets/disks/delete.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. +# 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 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_unary(project=project_id, zone=zone, disk=disk_name) + operation_client = compute_v1.ZoneOperationsClient() + operation = operation_client.wait( + project=project_id, zone=zone, operation=operation.name + ) + + if operation.error: + print("Error during disk delete operation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warnings during disk delete operation:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + return + + +# [END compute_disk_delete] diff --git a/compute/compute/snippets/disks/list.py b/compute/compute/snippets/disks/list.py new file mode 100644 index 00000000000..2b522e2b982 --- /dev/null +++ b/compute/compute/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/compute/snippets/firewall/create.py b/compute/compute/snippets/firewall/create.py index 5bcf1c5fce9..1eb230dea75 100644 --- a/compute/compute/snippets/firewall/create.py +++ b/compute/compute/snippets/firewall/create.py @@ -36,6 +36,9 @@ def create_firewall_rule( * 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 @@ -57,7 +60,7 @@ def create_firewall_rule( # 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() diff --git a/compute/compute/snippets/firewall/delete.py b/compute/compute/snippets/firewall/delete.py index 8dbea87095e..d606912a518 100644 --- a/compute/compute/snippets/firewall/delete.py +++ b/compute/compute/snippets/firewall/delete.py @@ -23,9 +23,9 @@ from google.cloud import compute_v1 -def delete_firewall_rule(project_id: str, firewall_rule_name: str): +def delete_firewall_rule(project_id: str, firewall_rule_name: str) -> None: """ - Deleted a firewall rule from the project. + Deletes a firewall rule from the project. Args: project_id: project ID or project number of the Cloud project you want to use. diff --git a/compute/compute/snippets/firewall/list.py b/compute/compute/snippets/firewall/list.py index d4553ac1292..7a0636ae89c 100644 --- a/compute/compute/snippets/firewall/list.py +++ b/compute/compute/snippets/firewall/list.py @@ -34,7 +34,7 @@ def list_firewall_rules(project_id: str) -> Iterable[compute_v1.Firewall]: 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. + A flat list of all firewall rules defined for given project. """ firewall_client = compute_v1.FirewallsClient() firewalls_list = firewall_client.list(project=project_id) diff --git a/compute/compute/snippets/firewall/main.py b/compute/compute/snippets/firewall/main.py index d8b47a3380f..b53e677e07f 100644 --- a/compute/compute/snippets/firewall/main.py +++ b/compute/compute/snippets/firewall/main.py @@ -37,6 +37,9 @@ def create_firewall_rule( * 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 @@ -58,7 +61,7 @@ def create_firewall_rule( # 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() @@ -72,9 +75,9 @@ def create_firewall_rule( return firewall_client.get(project=project_id, firewall=firewall_rule_name) -def delete_firewall_rule(project_id: str, firewall_rule_name: str): +def delete_firewall_rule(project_id: str, firewall_rule_name: str) -> None: """ - Deleted a firewall rule from the project. + Deletes a firewall rule from the project. Args: project_id: project ID or project number of the Cloud project you want to use. @@ -91,6 +94,16 @@ def delete_firewall_rule(project_id: str, firewall_rule_name: str): 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) @@ -104,7 +117,7 @@ def list_firewall_rules(project_id: str) -> Iterable[compute_v1.Firewall]: 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. + A flat list of all firewall rules defined for given project. """ firewall_client = compute_v1.FirewallsClient() firewalls_list = firewall_client.list(project=project_id) @@ -115,7 +128,9 @@ def list_firewall_rules(project_id: str) -> Iterable[compute_v1.Firewall]: return firewalls_list -def patch_firewall_priority(project_id: str, firewall_rule_name: str, priority: int): +def patch_firewall_priority( + project_id: str, firewall_rule_name: str, priority: int +) -> None: """ Modifies the priority of a given firewall rule. diff --git a/compute/compute/snippets/firewall/patch.py b/compute/compute/snippets/firewall/patch.py index e5f2a96d676..9dbb823da3f 100644 --- a/compute/compute/snippets/firewall/patch.py +++ b/compute/compute/snippets/firewall/patch.py @@ -23,7 +23,9 @@ from google.cloud import compute_v1 -def patch_firewall_priority(project_id: str, firewall_rule_name: str, priority: int): +def patch_firewall_priority( + project_id: str, firewall_rule_name: str, priority: int +) -> None: """ Modifies the priority of a given firewall rule. diff --git a/compute/compute/snippets/images/get.py b/compute/compute/snippets/images/get.py index 94c8f3af468..dbf8b3a2625 100644 --- a/compute/compute/snippets/images/get.py +++ b/compute/compute/snippets/images/get.py @@ -27,6 +27,16 @@ 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) diff --git a/compute/compute/snippets/images/pagination.py b/compute/compute/snippets/images/pagination.py index 7ac6d0b6e59..603480b15c0 100644 --- a/compute/compute/snippets/images/pagination.py +++ b/compute/compute/snippets/images/pagination.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright 2021 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/compute/compute/snippets/instances/create.py b/compute/compute/snippets/instances/create.py index 73d2d806cfe..a4df25156e2 100644 --- a/compute/compute/snippets/instances/create.py +++ b/compute/compute/snippets/instances/create.py @@ -22,12 +22,23 @@ # [START compute_instances_create] import re import sys +import time from typing import List 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) @@ -35,7 +46,11 @@ def get_image_from_family(project: str, family: str) -> compute_v1.Image: def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -50,6 +65,7 @@ def disk_from_image( 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. @@ -62,7 +78,7 @@ def disk_from_image( 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 = True + boot_disk.auto_delete = auto_delete boot_disk.boot = boot return boot_disk @@ -75,6 +91,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -86,17 +106,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -115,6 +145,18 @@ def create_instance( 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.name = instance_name @@ -124,6 +166,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -139,13 +184,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -156,12 +194,16 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py b/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py index 0cea574075a..203633f02fd 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py @@ -22,12 +22,23 @@ # [START compute_instances_create_from_custom_image] import re import sys +import time from typing import List 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) @@ -35,7 +46,11 @@ def get_image_from_family(project: str, family: str) -> compute_v1.Image: def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -50,6 +65,7 @@ def disk_from_image( 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. @@ -62,7 +78,7 @@ def disk_from_image( 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 = True + boot_disk.auto_delete = auto_delete boot_disk.boot = boot return boot_disk @@ -75,6 +91,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -86,17 +106,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -115,6 +145,18 @@ def create_instance( 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.name = instance_name @@ -124,6 +166,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -139,13 +184,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -156,12 +194,16 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py b/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py index 0d309604a46..28d762a86a7 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py @@ -22,12 +22,23 @@ # [START compute_instances_create_from_image] import re import sys +import time from typing import List 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) @@ -35,7 +46,11 @@ def get_image_from_family(project: str, family: str) -> compute_v1.Image: def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -50,6 +65,7 @@ def disk_from_image( 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. @@ -62,7 +78,7 @@ def disk_from_image( 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 = True + boot_disk.auto_delete = auto_delete boot_disk.boot = boot return boot_disk @@ -75,6 +91,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -86,17 +106,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -115,6 +145,18 @@ def create_instance( 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.name = instance_name @@ -124,6 +166,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -139,13 +184,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -156,12 +194,16 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py b/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py index bc7b01c2600..92880c972ae 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py @@ -22,13 +22,18 @@ # [START compute_instances_create_from_snapshot] import re import sys +import time from typing import List from google.cloud import compute_v1 def disk_from_snapshot( - disk_type: str, disk_size_gb: int, boot: bool, disk_snapshot: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_snapshot: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk(): """ Create an AttachedDisk object to be used in VM instance creation. Uses a disk snapshot as the @@ -40,21 +45,22 @@ def disk_from_snapshot( 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 - disk_snapshot: disk snapshot to use when creating this disk. You must have read access to this disk. + 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 = disk_snapshot + 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 = True + disk.auto_delete = auto_delete disk.boot = boot return disk @@ -67,6 +73,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -78,17 +88,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -107,6 +127,18 @@ def create_instance( 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.name = instance_name @@ -116,6 +148,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -131,13 +166,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -148,12 +176,16 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") @@ -164,7 +196,8 @@ 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. + 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. @@ -177,7 +210,7 @@ def create_from_snapshot( Instance object. """ disk_type = f"zones/{zone}/diskTypes/pd-standard" - disks = [disk_from_snapshot(disk_type, 11, True, snapshot_link)] + 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/compute/snippets/instances/create_start_instance/create_with_additional_disk.py b/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py index 7945638de8a..bffe080107b 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py @@ -22,12 +22,23 @@ # [START compute_instances_create_from_image_plus_empty_disk] import re import sys +import time from typing import List 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) @@ -35,7 +46,11 @@ def get_image_from_family(project: str, family: str) -> compute_v1.Image: def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -50,6 +65,7 @@ def disk_from_image( 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. @@ -62,12 +78,14 @@ def disk_from_image( 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 = True + boot_disk.auto_delete = auto_delete boot_disk.boot = boot return boot_disk -def empty_disk(disk_type: str, disk_size_gb: int) -> compute_v1.AttachedDisk(): +def empty_disk( + disk_type: str, disk_size_gb: int, boot: bool = False, auto_delete: bool = False +) -> 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. @@ -77,6 +95,8 @@ def empty_disk(disk_type: str, disk_size_gb: int) -> compute_v1.AttachedDisk(): "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. @@ -101,6 +121,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -112,17 +136,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -141,6 +175,18 @@ def create_instance( 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.name = instance_name @@ -150,6 +196,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -165,13 +214,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -182,12 +224,16 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") @@ -198,8 +244,8 @@ 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 and a 11 GB additional - empty disk. + 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. @@ -212,8 +258,8 @@ def create_with_additional_disk( 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), - empty_disk(disk_type, 11), + 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 diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py b/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py index 134f41c0153..f1c904d0990 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py @@ -22,12 +22,23 @@ # [START compute_instances_create_from_image_plus_snapshot_disk] import re import sys +import time from typing import List 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) @@ -35,7 +46,11 @@ def get_image_from_family(project: str, family: str) -> compute_v1.Image: def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -50,6 +65,7 @@ def disk_from_image( 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. @@ -62,13 +78,17 @@ def disk_from_image( 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 = True + 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, disk_snapshot: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_snapshot: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk(): """ Create an AttachedDisk object to be used in VM instance creation. Uses a disk snapshot as the @@ -80,21 +100,22 @@ def disk_from_snapshot( 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 - disk_snapshot: disk snapshot to use when creating this disk. You must have read access to this disk. + 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 = disk_snapshot + 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 = True + disk.auto_delete = auto_delete disk.boot = boot return disk @@ -107,6 +128,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -118,17 +143,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -147,6 +182,18 @@ def create_instance( 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.name = instance_name @@ -156,6 +203,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -171,13 +221,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -188,12 +231,16 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") diff --git a/compute/compute/snippets/instances/create_with_subnet.py b/compute/compute/snippets/instances/create_with_subnet.py index 63e46d0ccf4..6c4be012284 100644 --- a/compute/compute/snippets/instances/create_with_subnet.py +++ b/compute/compute/snippets/instances/create_with_subnet.py @@ -22,12 +22,23 @@ # [START compute_instances_create_with_subnet] import re import sys +import time from typing import List 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) @@ -35,7 +46,11 @@ def get_image_from_family(project: str, family: str) -> compute_v1.Image: def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -50,6 +65,7 @@ def disk_from_image( 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. @@ -62,7 +78,7 @@ def disk_from_image( 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 = True + boot_disk.auto_delete = auto_delete boot_disk.boot = boot return boot_disk @@ -75,6 +91,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -86,17 +106,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -115,6 +145,18 @@ def create_instance( 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.name = instance_name @@ -124,6 +166,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -139,13 +184,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -156,12 +194,16 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") diff --git a/compute/compute/snippets/instances/custom_hostname/create.py b/compute/compute/snippets/instances/custom_hostname/create.py index c600f6c17f4..9ede42b7b73 100644 --- a/compute/compute/snippets/instances/custom_hostname/create.py +++ b/compute/compute/snippets/instances/custom_hostname/create.py @@ -22,12 +22,23 @@ # [START compute_instances_create_custom_hostname] import re import sys +import time from typing import List 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) @@ -35,7 +46,11 @@ def get_image_from_family(project: str, family: str) -> compute_v1.Image: def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -50,6 +65,7 @@ def disk_from_image( 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. @@ -62,7 +78,7 @@ def disk_from_image( 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 = True + boot_disk.auto_delete = auto_delete boot_disk.boot = boot return boot_disk @@ -75,6 +91,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -86,17 +106,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -115,6 +145,18 @@ def create_instance( 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.name = instance_name @@ -124,6 +166,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -139,13 +184,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -156,12 +194,16 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") diff --git a/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py index 16d10788391..f699228b998 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py @@ -25,6 +25,7 @@ from enum import unique import re import sys +import time from typing import List from google.cloud import compute_v1 @@ -60,6 +61,8 @@ class CPUSeries(Enum): ], ) + # 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), @@ -86,10 +89,12 @@ def __init__( 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._check() + self._checked = False + self._check_parameters() self.extra_memory_used = self._check_extra_memory() def is_shared(self): @@ -100,10 +105,14 @@ def is_shared(self): ) def _check_extra_memory(self) -> bool: - # Assuming this runs after _check() and the total memory requested is correct - return self.memory_mb > self.core_count * self.limits.max_mem_per_core + 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(self): + 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 @@ -139,6 +148,8 @@ def _check(self): 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. @@ -212,6 +223,16 @@ def from_str(cls, machine_type: str): 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) @@ -219,7 +240,11 @@ def get_image_from_family(project: str, family: str) -> compute_v1.Image: def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -234,6 +259,7 @@ def disk_from_image( 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. @@ -246,7 +272,7 @@ def disk_from_image( 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 = True + boot_disk.auto_delete = auto_delete boot_disk.boot = boot return boot_disk @@ -259,6 +285,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -270,17 +300,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -299,6 +339,18 @@ def create_instance( 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.name = instance_name @@ -308,6 +360,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -323,13 +378,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -340,12 +388,16 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") diff --git a/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py index 79e7d1a2a01..700ccdcea22 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py @@ -25,6 +25,7 @@ from enum import unique import re import sys +import time from typing import List from google.cloud import compute_v1 @@ -60,6 +61,8 @@ class CPUSeries(Enum): ], ) + # 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), @@ -86,10 +89,12 @@ def __init__( 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._check() + self._checked = False + self._check_parameters() self.extra_memory_used = self._check_extra_memory() def is_shared(self): @@ -100,10 +105,14 @@ def is_shared(self): ) def _check_extra_memory(self) -> bool: - # Assuming this runs after _check() and the total memory requested is correct - return self.memory_mb > self.core_count * self.limits.max_mem_per_core + 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(self): + 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 @@ -139,6 +148,8 @@ def _check(self): 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. @@ -212,6 +223,16 @@ def from_str(cls, machine_type: str): 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) @@ -219,7 +240,11 @@ def get_image_from_family(project: str, family: str) -> compute_v1.Image: def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -234,6 +259,7 @@ def disk_from_image( 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. @@ -246,7 +272,7 @@ def disk_from_image( 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 = True + boot_disk.auto_delete = auto_delete boot_disk.boot = boot return boot_disk @@ -259,6 +285,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -270,17 +300,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -299,6 +339,18 @@ def create_instance( 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.name = instance_name @@ -308,6 +360,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -323,13 +378,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -340,12 +388,16 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") diff --git a/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py index 5f04cc79f6c..ae79f9fb7f9 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py @@ -22,12 +22,23 @@ # [START compute_custom_machine_type_create_without_helper] import re import sys +import time from typing import List 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) @@ -35,7 +46,11 @@ def get_image_from_family(project: str, family: str) -> compute_v1.Image: def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -50,6 +65,7 @@ def disk_from_image( 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. @@ -62,7 +78,7 @@ def disk_from_image( 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 = True + boot_disk.auto_delete = auto_delete boot_disk.boot = boot return boot_disk @@ -75,6 +91,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -86,17 +106,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -115,6 +145,18 @@ def create_instance( 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.name = instance_name @@ -124,6 +166,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -139,13 +184,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -156,12 +194,16 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") @@ -172,7 +214,7 @@ def create_custom_instances_no_helper( project_id: str, zone: str, instance_name: str, core_count: int, memory: int ) -> List[compute_v1.Instance]: """ - Create new VM instances without using a CustomMachineType helper function. + 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. @@ -187,58 +229,40 @@ def create_custom_instances_no_helper( 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, + params = [ + ( f"{instance_name}_n1", - disks, f"zones/{zone}/machineTypes/custom-{core_count}-{memory}", ), - create_instance( - project_id, - zone, + ( f"{instance_name}_n2", - disks, f"zones/{zone}/machineTypes/n2-custom-{core_count}-{memory}", ), - create_instance( - project_id, - zone, + ( f"{instance_name}_n2d", - disks, f"zones/{zone}/machineTypes/n2d-custom-{core_count}-{memory}", ), - create_instance( - project_id, - zone, + ( f"{instance_name}_e2", - disks, f"zones/{zone}/machineTypes/e2-custom-{core_count}-{memory}", ), - create_instance( - project_id, - zone, + ( f"{instance_name}_e2_micro", - disks, f"zones/{zone}/machineTypes/e2-custom-micro-{memory}", ), - create_instance( - project_id, - zone, + ( f"{instance_name}_e2_small", - disks, f"zones/{zone}/machineTypes/e2-custom-small-{memory}", ), - create_instance( - project_id, - zone, + ( f"{instance_name}_e2_medium", - disks, 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/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py b/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py index a2667437e56..e4b1abd1198 100644 --- a/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py @@ -22,12 +22,23 @@ # [START compute_custom_machine_type_extra_mem_no_helper] import re import sys +import time from typing import List 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) @@ -35,7 +46,11 @@ def get_image_from_family(project: str, family: str) -> compute_v1.Image: def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -50,6 +65,7 @@ def disk_from_image( 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. @@ -62,7 +78,7 @@ def disk_from_image( 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 = True + boot_disk.auto_delete = auto_delete boot_disk.boot = boot return boot_disk @@ -75,6 +91,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -86,17 +106,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -115,6 +145,18 @@ def create_instance( 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.name = instance_name @@ -124,6 +166,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -139,13 +184,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -156,23 +194,27 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") return instance -def create_custom_instances_extra_mem_no_helper( +def create_custom_instances_extra_mem( project_id: str, zone: str, instance_name: str, core_count: int, memory: int ) -> List[compute_v1.Instance]: """ - Create new VM instances with extra memory without using a CustomMachineType helper class. + 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. diff --git a/compute/compute/snippets/instances/custom_machine_types/helper_class.py b/compute/compute/snippets/instances/custom_machine_types/helper_class.py index acd867c0ff1..568710aee3f 100644 --- a/compute/compute/snippets/instances/custom_machine_types/helper_class.py +++ b/compute/compute/snippets/instances/custom_machine_types/helper_class.py @@ -55,6 +55,8 @@ class CPUSeries(Enum): ], ) + # 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), @@ -81,10 +83,12 @@ def __init__( 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._check() + self._checked = False + self._check_parameters() self.extra_memory_used = self._check_extra_memory() def is_shared(self): @@ -95,10 +99,14 @@ def is_shared(self): ) def _check_extra_memory(self) -> bool: - # Assuming this runs after _check() and the total memory requested is correct - return self.memory_mb > self.core_count * self.limits.max_mem_per_core + 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(self): + 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 @@ -134,6 +142,8 @@ def _check(self): 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. diff --git a/compute/compute/snippets/instances/custom_machine_types/update_memory.py b/compute/compute/snippets/instances/custom_machine_types/update_memory.py index 5a168e784f6..b65c9434402 100644 --- a/compute/compute/snippets/instances/custom_machine_types/update_memory.py +++ b/compute/compute/snippets/instances/custom_machine_types/update_memory.py @@ -46,6 +46,13 @@ def add_extended_memory_to_instance( 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, @@ -55,6 +62,7 @@ def add_extended_memory_to_instance( project=project_id, zone=zone, instance=instance_name ) operation_client.wait(project=project_id, zone=zone, operation=op.name) + start = time.time() while instance.status not in ( instance.Status.TERMINATED.name, instance.Status.STOPPED.name, @@ -64,10 +72,13 @@ def add_extended_memory_to_instance( 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 diff --git a/compute/compute/snippets/instances/delete.py b/compute/compute/snippets/instances/delete.py index be8c714fe18..55cdfe9c7d8 100644 --- a/compute/compute/snippets/instances/delete.py +++ b/compute/compute/snippets/instances/delete.py @@ -21,6 +21,7 @@ # [START compute_instances_delete] import sys +import time from google.cloud import compute_v1 @@ -41,12 +42,16 @@ def delete_instance(project_id: str, zone: str, machine_name: str) -> None: operation = instance_client.delete_unary( project=project_id, zone=zone, instance=machine_name ) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during deletion:", operation.error, file=sys.stderr) + return if operation.warnings: print("Warning during deletion:", operation.warnings, file=sys.stderr) print(f"Instance {machine_name} deleted.") diff --git a/compute/compute/snippets/instances/delete_protection/create.py b/compute/compute/snippets/instances/delete_protection/create.py index b4b21b7fc23..32148a25fcc 100644 --- a/compute/compute/snippets/instances/delete_protection/create.py +++ b/compute/compute/snippets/instances/delete_protection/create.py @@ -22,12 +22,23 @@ # [START compute_delete_protection_create] import re import sys +import time from typing import List 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) @@ -35,7 +46,11 @@ def get_image_from_family(project: str, family: str) -> compute_v1.Image: def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -50,6 +65,7 @@ def disk_from_image( 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. @@ -62,7 +78,7 @@ def disk_from_image( 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 = True + boot_disk.auto_delete = auto_delete boot_disk.boot = boot return boot_disk @@ -75,6 +91,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -86,17 +106,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -115,6 +145,18 @@ def create_instance( 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.name = instance_name @@ -124,6 +166,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -139,13 +184,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -156,12 +194,16 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") diff --git a/compute/compute/snippets/instances/delete_protection/get.py b/compute/compute/snippets/instances/delete_protection/get.py index d6ecfa398b0..35bf1dc1f01 100644 --- a/compute/compute/snippets/instances/delete_protection/get.py +++ b/compute/compute/snippets/instances/delete_protection/get.py @@ -31,7 +31,7 @@ def get_delete_protection(project_id: str, zone: str, instance_name: str) -> boo 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 state of the delete protection setting. + The boolean value of the delete protection setting. """ instance_client = compute_v1.InstancesClient() instance = instance_client.get( diff --git a/compute/compute/snippets/instances/delete_protection/set.py b/compute/compute/snippets/instances/delete_protection/set.py index e25269317d6..47c3b35ba22 100644 --- a/compute/compute/snippets/instances/delete_protection/set.py +++ b/compute/compute/snippets/instances/delete_protection/set.py @@ -25,13 +25,13 @@ 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 virtual machine to update. + instance_name: name of the instance to update. delete_protection: boolean value indicating if the virtual machine should be protected against deletion or not. """ diff --git a/compute/compute/snippets/instances/list_all.py b/compute/compute/snippets/instances/list_all.py index 9549de0f43a..3bfda0b9a3c 100644 --- a/compute/compute/snippets/instances/list_all.py +++ b/compute/compute/snippets/instances/list_all.py @@ -29,7 +29,7 @@ def list_all_instances( project_id: str, ) -> Dict[str, Iterable[compute_v1.Instance]]: """ - Return a dictionary of all instances present in a project, grouped by their zone. + 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. @@ -38,9 +38,9 @@ def list_all_instances( iterable collections of Instance objects as values. """ instance_client = compute_v1.InstancesClient() - # Use the `max_results` parameter to limit the number of results that the API returns per response page. 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) diff --git a/compute/compute/snippets/instances/preemptible/create_preemptible.py b/compute/compute/snippets/instances/preemptible/create_preemptible.py index 2f4569bc873..3fd4de32d73 100644 --- a/compute/compute/snippets/instances/preemptible/create_preemptible.py +++ b/compute/compute/snippets/instances/preemptible/create_preemptible.py @@ -22,12 +22,23 @@ # [START compute_preemptible_create] import re import sys +import time from typing import List 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) @@ -35,7 +46,11 @@ def get_image_from_family(project: str, family: str) -> compute_v1.Image: def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str + disk_type: str, + disk_size_gb: int, + boot: bool, + source_image: str, + auto_delete: bool = False, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -50,6 +65,7 @@ def disk_from_image( 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. @@ -62,7 +78,7 @@ def disk_from_image( 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 = True + boot_disk.auto_delete = auto_delete boot_disk.boot = boot return boot_disk @@ -75,6 +91,10 @@ def create_instance( 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, custom_hostname: str = None, delete_protection: bool = False, @@ -86,17 +106,27 @@ def create_instance( 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" - disks: a list of compute_v1.AttachedDisk objects describing the disks - you want to attach to your new 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}" + 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. custom_hostname: Custom hostname of the new VM instance. @@ -115,6 +145,18 @@ def create_instance( 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.name = instance_name @@ -124,6 +166,9 @@ def create_instance( else: instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" + if accelerators: + instance.guest_accelerators = accelerators + instance.network_interfaces = [network_interface] if preemptible: @@ -139,13 +184,6 @@ def create_instance( # Set the delete protection bit instance.deletion_protection = True - # Shielded Instance settings - # Values presented here are the defaults. - # instance.shielded_instance_config = compute_v1.ShieldedInstanceConfig() - # instance.shielded_instance_config.enable_secure_boot = False - # instance.shielded_instance_config.enable_vtpm = True - # instance.shielded_instance_config.enable_integrity_monitoring = True - # Prepare the request to insert an instance. request = compute_v1.InsertInstanceRequest() request.zone = zone @@ -156,12 +194,16 @@ def create_instance( print(f"Creating the {instance_name} instance in {zone}...") operation = instance_client.insert_unary(request=request) + start = time.time() while operation.status != compute_v1.Operation.Status.DONE: operation = operation_client.wait( operation=operation.name, zone=zone, project=project_id ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() if operation.error: print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") diff --git a/compute/compute/snippets/instances/reset.py b/compute/compute/snippets/instances/reset.py index 74bb94c2fbb..a04003f17bc 100644 --- a/compute/compute/snippets/instances/reset.py +++ b/compute/compute/snippets/instances/reset.py @@ -20,10 +20,12 @@ # [START compute_reset_instance] +import time + from google.cloud import compute_v1 -def reset_instance(project_id: str, zone: str, instance_name: str): +def reset_instance(project_id: str, zone: str, instance_name: str) -> None: """ Resets a stopped Google Compute Engine instance (with unencrypted disks). Args: @@ -38,8 +40,11 @@ def reset_instance(project_id: str, zone: str, instance_name: str): project=project_id, zone=zone, instance=instance_name ) + start = time.time() while op.status != compute_v1.Operation.Status.DONE: op = op_client.wait(operation=op.name, zone=zone, project=project_id) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() return diff --git a/compute/compute/snippets/instances/start.py b/compute/compute/snippets/instances/start.py index 9de984bef5d..4548a8cd9bb 100644 --- a/compute/compute/snippets/instances/start.py +++ b/compute/compute/snippets/instances/start.py @@ -20,10 +20,12 @@ # [START compute_start_instance] +import time + from google.cloud import compute_v1 -def start_instance(project_id: str, zone: str, instance_name: str): +def start_instance(project_id: str, zone: str, instance_name: str) -> None: """ Starts a stopped Google Compute Engine instance (with unencrypted disks). Args: @@ -38,8 +40,11 @@ def start_instance(project_id: str, zone: str, instance_name: str): project=project_id, zone=zone, instance=instance_name ) + start = time.time() while op.status != compute_v1.Operation.Status.DONE: op = op_client.wait(operation=op.name, zone=zone, project=project_id) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() return diff --git a/compute/compute/snippets/instances/start_encrypted.py b/compute/compute/snippets/instances/start_encrypted.py index c0d5c14e88c..401b2b0cc9a 100644 --- a/compute/compute/snippets/instances/start_encrypted.py +++ b/compute/compute/snippets/instances/start_encrypted.py @@ -20,6 +20,8 @@ # [START compute_start_enc_instance] +import time + from google.cloud import compute_v1 @@ -60,8 +62,11 @@ def start_instance_with_encryption_key( instances_start_with_encryption_key_request_resource=enc_data, ) + start = time.time() while op.status != compute_v1.Operation.Status.DONE: op = op_client.wait(operation=op.name, zone=zone, project=project_id) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() return diff --git a/compute/compute/snippets/instances/stop.py b/compute/compute/snippets/instances/stop.py index cf155c74c5b..b8b2f8b4e75 100644 --- a/compute/compute/snippets/instances/stop.py +++ b/compute/compute/snippets/instances/stop.py @@ -20,12 +20,14 @@ # [START compute_stop_instance] +import time + from google.cloud import compute_v1 -def stop_instance(project_id: str, zone: str, instance_name: str): +def stop_instance(project_id: str, zone: str, instance_name: str) -> None: """ - Stops a stopped Google Compute Engine instance. + 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. @@ -38,8 +40,11 @@ def stop_instance(project_id: str, zone: str, instance_name: str): project=project_id, zone=zone, instance=instance_name ) + start = time.time() while op.status != compute_v1.Operation.Status.DONE: op = op_client.wait(operation=op.name, zone=zone, project=project_id) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() return diff --git a/compute/compute/snippets/snapshots/__init__.py b/compute/compute/snippets/snapshots/__init__.py new file mode 100644 index 00000000000..4bbe0ffdb06 --- /dev/null +++ b/compute/compute/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/compute/snippets/snapshots/create.py b/compute/compute/snippets/snapshots/create.py new file mode 100644 index 00000000000..adfdf8e28d3 --- /dev/null +++ b/compute/compute/snippets/snapshots/create.py @@ -0,0 +1,67 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 google.cloud import compute_v1 + + +def create_snapshot( + project_id: str, zone: str, disk_name: str, snapshot_name: str +) -> compute_v1.Snapshot: + """ + Create a snapshot of a disk. + + 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 snapshot. + disk_name: name of the disk you want to snapshot. + snapshot_name: name of the snapshot to be created. + + Returns: + The new snapshot instance. + """ + disk_client = compute_v1.DisksClient() + disk = disk_client.get(project=project_id, zone=zone, disk=disk_name) + snapshot = compute_v1.Snapshot() + snapshot.source_disk = disk.self_link + snapshot.name = snapshot_name + + snapshot_client = compute_v1.SnapshotsClient() + operation = snapshot_client.insert_unary( + project=project_id, snapshot_resource=snapshot + ) + op_client = compute_v1.GlobalOperationsClient() + operation = op_client.wait(project=project_id, operation=operation.name) + + if operation.error: + print("Error during snapshot creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warnings during snapshot creation:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return snapshot_client.get(project=project_id, snapshot=snapshot_name) + + +# [END compute_snapshot_create] diff --git a/compute/compute/snippets/snapshots/delete.py b/compute/compute/snippets/snapshots/delete.py new file mode 100644 index 00000000000..ffa0abab522 --- /dev/null +++ b/compute/compute/snippets/snapshots/delete.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. +# 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 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_unary(project=project_id, snapshot=snapshot_name) + op_client = compute_v1.GlobalOperationsClient() + operation = op_client.wait(project=project_id, operation=operation.name) + + if operation.error: + print("Error during snapshot deletion:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warnings during snapshot deletion:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return + + +# [END compute_snapshot_delete] diff --git a/compute/compute/snippets/snapshots/list.py b/compute/compute/snippets/snapshots/list.py new file mode 100644 index 00000000000..f7db91dbbdc --- /dev/null +++ b/compute/compute/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/compute/snippets/tests/test_sample_create_vm.py b/compute/compute/snippets/tests/test_create_vm.py similarity index 98% rename from compute/compute/snippets/tests/test_sample_create_vm.py rename to compute/compute/snippets/tests/test_create_vm.py index b08617f6e24..5497f233bb1 100644 --- a/compute/compute/snippets/tests/test_sample_create_vm.py +++ b/compute/compute/snippets/tests/test_create_vm.py @@ -152,10 +152,10 @@ def test_create_with_additional_disk(self): instance = create_with_additional_disk(PROJECT, INSTANCE_ZONE, instance_name) try: assert any( - disk.initialize_params.disk_size_gb == 11 for disk in instance.disks + disk.initialize_params.disk_size_gb == 20 for disk in instance.disks ) assert any( - disk.initialize_params.disk_size_gb == 10 for disk in instance.disks + disk.initialize_params.disk_size_gb == 25 for disk in instance.disks ) assert len(instance.disks) == 2 finally: diff --git a/compute/compute/snippets/tests/test_sample_custom_types.py b/compute/compute/snippets/tests/test_custom_types.py similarity index 100% rename from compute/compute/snippets/tests/test_sample_custom_types.py rename to compute/compute/snippets/tests/test_custom_types.py diff --git a/compute/compute/snippets/tests/test_sample_default_values.py b/compute/compute/snippets/tests/test_default_values.py similarity index 100% rename from compute/compute/snippets/tests/test_sample_default_values.py rename to compute/compute/snippets/tests/test_default_values.py diff --git a/compute/compute/snippets/tests/test_disks.py b/compute/compute/snippets/tests/test_disks.py new file mode 100644 index 00000000000..e0d3675543c --- /dev/null +++ b/compute/compute/snippets/tests/test_disks.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. +import uuid + +from google.api_core.exceptions import NotFound +import google.auth +import pytest + +from ..disks.create_from_image import create_disk_from_image +from ..disks.delete import delete_disk +from ..disks.list import list_disks +from ..images.get import get_image_from_family + +PROJECT = google.auth.default()[1] +ZONE = 'europe-north1-c' + + +@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 + + +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.") diff --git a/compute/compute/snippets/tests/test_sample_firewall.py b/compute/compute/snippets/tests/test_firewall.py similarity index 100% rename from compute/compute/snippets/tests/test_sample_firewall.py rename to compute/compute/snippets/tests/test_firewall.py diff --git a/compute/compute/snippets/tests/test_sample_images.py b/compute/compute/snippets/tests/test_images.py similarity index 100% rename from compute/compute/snippets/tests/test_sample_images.py rename to compute/compute/snippets/tests/test_images.py diff --git a/compute/compute/snippets/tests/test_sample_instance_from_template.py b/compute/compute/snippets/tests/test_instance_from_template.py similarity index 100% rename from compute/compute/snippets/tests/test_sample_instance_from_template.py rename to compute/compute/snippets/tests/test_instance_from_template.py diff --git a/compute/compute/snippets/tests/test_sample_start_stop.py b/compute/compute/snippets/tests/test_instance_start_stop.py similarity index 100% rename from compute/compute/snippets/tests/test_sample_start_stop.py rename to compute/compute/snippets/tests/test_instance_start_stop.py diff --git a/compute/compute/snippets/tests/test_sample_pagination.py b/compute/compute/snippets/tests/test_pagination.py similarity index 100% rename from compute/compute/snippets/tests/test_sample_pagination.py rename to compute/compute/snippets/tests/test_pagination.py diff --git a/compute/compute/snippets/tests/test_snapshots.py b/compute/compute/snippets/tests/test_snapshots.py new file mode 100644 index 00000000000..47c2f5f1db1 --- /dev/null +++ b/compute/compute/snippets/tests/test_snapshots.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 ..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.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, ZONE, test_disk.name, snapshot_name) + 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.") + + 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/compute/snippets/tests/test_sample_templates.py b/compute/compute/snippets/tests/test_templates.py similarity index 98% rename from compute/compute/snippets/tests/test_sample_templates.py rename to compute/compute/snippets/tests/test_templates.py index 624a6f0178a..bf04fa12b8a 100644 --- a/compute/compute/snippets/tests/test_sample_templates.py +++ b/compute/compute/snippets/tests/test_templates.py @@ -19,7 +19,7 @@ # Turning off F401 check because flake8 doesn't recognize using # PyTest fixture as parameter as usage. -from .test_sample_start_stop import compute_instance # noqa: F401 +from .test_instance_start_stop import compute_instance # noqa: F401 from ..instance_templates.create import create_template from ..instance_templates.create_from_instance import \ diff --git a/compute/compute/snippets/usage_report/usage_reports.py b/compute/compute/snippets/usage_report/usage_reports.py index 54af6034bca..85e5ea9cd49 100644 --- a/compute/compute/snippets/usage_report/usage_reports.py +++ b/compute/compute/snippets/usage_report/usage_reports.py @@ -14,7 +14,7 @@ # flake8: noqa """ A sample script showing how to handle default values when communicating -with the Compute Engine API. +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. From 0adb8b6959177798019d5bd1adad602ee1ce1f8a Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Sat, 26 Mar 2022 12:26:17 +0100 Subject: [PATCH 072/113] docs(samples): Add samples for moving VM to different regions/zones (#244) Co-authored-by: Anthonios Partheniou --- .../ingredients/disks/create_empty_disk.py | 63 ++++++ .../ingredients/disks/disk_from_snapshot.py | 2 +- .../compute/ingredients/disks/empty_disk.py | 6 +- .../compute/ingredients/disks/from_image.py | 2 +- compute/compute/ingredients/disks/get.py | 37 +++ .../ingredients/instances/create_instance.py | 2 +- .../create_from_custom_image.py | 21 +- .../create_from_public_image.py | 2 +- .../create_with_existing_disks.py | 50 +++++ compute/compute/ingredients/instances/get.py | 39 ++++ .../recipes/disks/create_empty_disk.py | 21 ++ .../create_with_existing_disks.py | 27 +++ compute/compute/recipes/instances/get.py | 20 ++ .../snippets/disks/create_empty_disk.py | 72 ++++++ compute/compute/snippets/instances/create.py | 4 +- .../create_from_custom_image.py | 6 +- .../create_from_public_image.py | 6 +- .../create_from_snapshot.py | 4 +- .../create_with_additional_disk.py | 10 +- .../create_with_existing_disks.py | 200 +++++++++++++++++ .../create_with_snapshotted_data_disk.py | 6 +- .../snippets/instances/create_with_subnet.py | 4 +- .../instances/custom_hostname/create.py | 4 +- .../create_shared_with_helper.py | 4 +- .../create_with_helper.py | 4 +- .../create_without_helper.py | 4 +- .../extra_mem_no_helper.py | 4 +- .../instances/delete_protection/create.py | 4 +- compute/compute/snippets/instances/get.py | 45 ++++ .../preemptible/create_preemptible.py | 4 +- .../compute/snippets/tests/test_create_vm.py | 210 +++++++++++------- 31 files changed, 745 insertions(+), 142 deletions(-) create mode 100644 compute/compute/ingredients/disks/create_empty_disk.py create mode 100644 compute/compute/ingredients/disks/get.py create mode 100644 compute/compute/ingredients/instances/create_start_instance/create_with_existing_disks.py create mode 100644 compute/compute/ingredients/instances/get.py create mode 100644 compute/compute/recipes/disks/create_empty_disk.py create mode 100644 compute/compute/recipes/instances/create_start_instance/create_with_existing_disks.py create mode 100644 compute/compute/recipes/instances/get.py create mode 100644 compute/compute/snippets/disks/create_empty_disk.py create mode 100644 compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py create mode 100644 compute/compute/snippets/instances/get.py diff --git a/compute/compute/ingredients/disks/create_empty_disk.py b/compute/compute/ingredients/disks/create_empty_disk.py new file mode 100644 index 00000000000..7422caf45e6 --- /dev/null +++ b/compute/compute/ingredients/disks/create_empty_disk.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 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_unary(project=project_id, zone=zone, disk_resource=disk) + operation_client = compute_v1.ZoneOperationsClient() + operation = operation_client.wait(project=project_id, zone=zone, operation=operation.name) + + if operation.error: + print("Error during disk creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warnings during disk creation:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return disk_client.get(project=project_id, zone=zone, disk=disk.name) +# diff --git a/compute/compute/ingredients/disks/disk_from_snapshot.py b/compute/compute/ingredients/disks/disk_from_snapshot.py index e0271c47bc4..f7abd0c5a68 100644 --- a/compute/compute/ingredients/disks/disk_from_snapshot.py +++ b/compute/compute/ingredients/disks/disk_from_snapshot.py @@ -21,7 +21,7 @@ # def disk_from_snapshot( - disk_type: str, disk_size_gb: int, boot: bool, source_snapshot: str, auto_delete: bool = False + 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 diff --git a/compute/compute/ingredients/disks/empty_disk.py b/compute/compute/ingredients/disks/empty_disk.py index 570292fde9e..70c677b7da3 100644 --- a/compute/compute/ingredients/disks/empty_disk.py +++ b/compute/compute/ingredients/disks/empty_disk.py @@ -20,7 +20,7 @@ # -def empty_disk(disk_type: str, disk_size_gb: int, boot: bool = False, auto_delete: bool = False) -> compute_v1.AttachedDisk(): +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. @@ -43,7 +43,7 @@ def empty_disk(disk_type: str, disk_size_gb: int, boot: bool = False, auto_delet 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 = True - disk.boot = False + disk.auto_delete = auto_delete + disk.boot = boot return disk # diff --git a/compute/compute/ingredients/disks/from_image.py b/compute/compute/ingredients/disks/from_image.py index 5f22f4e6176..945b018b9f8 100644 --- a/compute/compute/ingredients/disks/from_image.py +++ b/compute/compute/ingredients/disks/from_image.py @@ -21,7 +21,7 @@ # def disk_from_image( - disk_type: str, disk_size_gb: int, boot: bool, source_image: str, auto_delete: bool = False + 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 diff --git a/compute/compute/ingredients/disks/get.py b/compute/compute/ingredients/disks/get.py new file mode 100644 index 00000000000..54b68d9d4ae --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/create_instance.py b/compute/compute/ingredients/instances/create_instance.py index 99fad23e414..17ab8319b13 100644 --- a/compute/compute/ingredients/instances/create_instance.py +++ b/compute/compute/ingredients/instances/create_instance.py @@ -149,5 +149,5 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) # diff --git a/compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.py b/compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.py index 2d297cbca98..1195b75872f 100644 --- a/compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.py +++ b/compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.py @@ -1,22 +1,3 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT 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 -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -55,7 +36,7 @@ def create_from_custom_image( Instance object. """ disk_type = f"zones/{zone}/diskTypes/pd-standard" - disks = [disk_from_image(disk_type, 10, True, custom_image_link)] + 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/compute/ingredients/instances/create_start_instance/create_from_public_image.py b/compute/compute/ingredients/instances/create_start_instance/create_from_public_image.py index 2eb8e3c2e15..fdf2e35f748 100644 --- a/compute/compute/ingredients/instances/create_start_instance/create_from_public_image.py +++ b/compute/compute/ingredients/instances/create_start_instance/create_from_public_image.py @@ -36,7 +36,7 @@ def create_from_public_image(project_id: str, zone: str, instance_name: str) -> 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)] + 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/compute/ingredients/instances/create_start_instance/create_with_existing_disks.py b/compute/compute/ingredients/instances/create_start_instance/create_with_existing_disks.py new file mode 100644 index 00000000000..2affc851d47 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/get.py b/compute/compute/ingredients/instances/get.py new file mode 100644 index 00000000000..702696ad789 --- /dev/null +++ b/compute/compute/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/compute/recipes/disks/create_empty_disk.py b/compute/compute/recipes/disks/create_empty_disk.py new file mode 100644 index 00000000000..923f79d076c --- /dev/null +++ b/compute/compute/recipes/disks/create_empty_disk.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/compute/recipes/instances/create_start_instance/create_with_existing_disks.py b/compute/compute/recipes/instances/create_start_instance/create_with_existing_disks.py new file mode 100644 index 00000000000..3f6bf6ac20f --- /dev/null +++ b/compute/compute/recipes/instances/create_start_instance/create_with_existing_disks.py @@ -0,0 +1,27 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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/compute/recipes/instances/get.py b/compute/compute/recipes/instances/get.py new file mode 100644 index 00000000000..2dcc5c8849e --- /dev/null +++ b/compute/compute/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/compute/snippets/disks/create_empty_disk.py b/compute/compute/snippets/disks/create_empty_disk.py new file mode 100644 index 00000000000..d1352f861ed --- /dev/null +++ b/compute/compute/snippets/disks/create_empty_disk.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. +# 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 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_unary( + project=project_id, zone=zone, disk_resource=disk + ) + operation_client = compute_v1.ZoneOperationsClient() + operation = operation_client.wait( + project=project_id, zone=zone, operation=operation.name + ) + + if operation.error: + print("Error during disk creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warnings during disk creation:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return disk_client.get(project=project_id, zone=zone, disk=disk.name) + + +# [END compute_disk_create_empty_disk] diff --git a/compute/compute/snippets/instances/create.py b/compute/compute/snippets/instances/create.py index a4df25156e2..efa0de1735a 100644 --- a/compute/compute/snippets/instances/create.py +++ b/compute/compute/snippets/instances/create.py @@ -50,7 +50,7 @@ def disk_from_image( disk_size_gb: int, boot: bool, source_image: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -207,7 +207,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) # [END compute_instances_create] diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py b/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py index 203633f02fd..59d32f08c14 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py @@ -50,7 +50,7 @@ def disk_from_image( disk_size_gb: int, boot: bool, source_image: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -207,7 +207,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) def create_from_custom_image( @@ -227,7 +227,7 @@ def create_from_custom_image( Instance object. """ disk_type = f"zones/{zone}/diskTypes/pd-standard" - disks = [disk_from_image(disk_type, 10, True, custom_image_link)] + 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/compute/snippets/instances/create_start_instance/create_from_public_image.py b/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py index 28d762a86a7..bfe848afaf8 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py @@ -50,7 +50,7 @@ def disk_from_image( disk_size_gb: int, boot: bool, source_image: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -207,7 +207,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) def create_from_public_image( @@ -226,7 +226,7 @@ def create_from_public_image( """ 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)] + 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/compute/snippets/instances/create_start_instance/create_from_snapshot.py b/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py index 92880c972ae..2c6996ed89a 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py @@ -33,7 +33,7 @@ def disk_from_snapshot( disk_size_gb: int, boot: bool, source_snapshot: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk(): """ Create an AttachedDisk object to be used in VM instance creation. Uses a disk snapshot as the @@ -189,7 +189,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) def create_from_snapshot( diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py b/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py index bffe080107b..a14005eb5d9 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py @@ -50,7 +50,7 @@ def disk_from_image( disk_size_gb: int, boot: bool, source_image: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -84,7 +84,7 @@ def disk_from_image( def empty_disk( - disk_type: str, disk_size_gb: int, boot: bool = False, auto_delete: bool = False + 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 @@ -108,8 +108,8 @@ def empty_disk( 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 = True - disk.boot = False + disk.auto_delete = auto_delete + disk.boot = boot return disk @@ -237,7 +237,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) def create_with_additional_disk( diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py b/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py new file mode 100644 index 00000000000..62a118ee850 --- /dev/null +++ b/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py @@ -0,0 +1,200 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 +import time +from typing import Iterable, List, NoReturn + +from google.cloud import compute_v1 + + +def get_disk(project_id: str, zone: str, disk_name: str) -> 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. + 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 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, + 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. + 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() + operation_client = compute_v1.ZoneOperationsClient() + + # 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.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 + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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_unary(request=request) + start = time.time() + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + raise RuntimeError(operation.error) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py b/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py index f1c904d0990..0385c17abc4 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py @@ -50,7 +50,7 @@ def disk_from_image( disk_size_gb: int, boot: bool, source_image: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -88,7 +88,7 @@ def disk_from_snapshot( disk_size_gb: int, boot: bool, source_snapshot: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk(): """ Create an AttachedDisk object to be used in VM instance creation. Uses a disk snapshot as the @@ -244,7 +244,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) def create_with_snapshotted_data_disk( diff --git a/compute/compute/snippets/instances/create_with_subnet.py b/compute/compute/snippets/instances/create_with_subnet.py index 6c4be012284..27a52a9f75c 100644 --- a/compute/compute/snippets/instances/create_with_subnet.py +++ b/compute/compute/snippets/instances/create_with_subnet.py @@ -50,7 +50,7 @@ def disk_from_image( disk_size_gb: int, boot: bool, source_image: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -207,7 +207,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) def create_with_subnet( diff --git a/compute/compute/snippets/instances/custom_hostname/create.py b/compute/compute/snippets/instances/custom_hostname/create.py index 9ede42b7b73..699f6397e75 100644 --- a/compute/compute/snippets/instances/custom_hostname/create.py +++ b/compute/compute/snippets/instances/custom_hostname/create.py @@ -50,7 +50,7 @@ def disk_from_image( disk_size_gb: int, boot: bool, source_image: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -207,7 +207,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) def create_instance_custom_hostname( diff --git a/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py index f699228b998..7674013d44e 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py @@ -244,7 +244,7 @@ def disk_from_image( disk_size_gb: int, boot: bool, source_image: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -401,7 +401,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) def create_custom_shared_core_instance( diff --git a/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py index 700ccdcea22..5821f160b59 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py @@ -244,7 +244,7 @@ def disk_from_image( disk_size_gb: int, boot: bool, source_image: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -401,7 +401,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) def create_custom_instance( diff --git a/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py index ae79f9fb7f9..54e208b6df7 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py @@ -50,7 +50,7 @@ def disk_from_image( disk_size_gb: int, boot: bool, source_image: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -207,7 +207,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) def create_custom_instances_no_helper( diff --git a/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py b/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py index e4b1abd1198..9449ef11f51 100644 --- a/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py @@ -50,7 +50,7 @@ def disk_from_image( disk_size_gb: int, boot: bool, source_image: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -207,7 +207,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) def create_custom_instances_extra_mem( diff --git a/compute/compute/snippets/instances/delete_protection/create.py b/compute/compute/snippets/instances/delete_protection/create.py index 32148a25fcc..15d31f102a8 100644 --- a/compute/compute/snippets/instances/delete_protection/create.py +++ b/compute/compute/snippets/instances/delete_protection/create.py @@ -50,7 +50,7 @@ def disk_from_image( disk_size_gb: int, boot: bool, source_image: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -207,7 +207,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) def create_protected_instance( diff --git a/compute/compute/snippets/instances/get.py b/compute/compute/snippets/instances/get.py new file mode 100644 index 00000000000..427ea19a1d8 --- /dev/null +++ b/compute/compute/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/compute/snippets/instances/preemptible/create_preemptible.py b/compute/compute/snippets/instances/preemptible/create_preemptible.py index 3fd4de32d73..5cc7ff47d4a 100644 --- a/compute/compute/snippets/instances/preemptible/create_preemptible.py +++ b/compute/compute/snippets/instances/preemptible/create_preemptible.py @@ -50,7 +50,7 @@ def disk_from_image( disk_size_gb: int, boot: bool, source_image: str, - auto_delete: bool = False, + auto_delete: bool = True, ) -> compute_v1.AttachedDisk: """ Create an AttachedDisk object to be used in VM instance creation. Uses an image as the @@ -207,7 +207,7 @@ def create_instance( if operation.warnings: print("Warning during creation:", operation.warnings, file=sys.stderr) print(f"Instance {instance_name} created.") - return instance + return instance_client.get(project=project_id, zone=zone, instance=instance_name) def create_preemptible_instance( diff --git a/compute/compute/snippets/tests/test_create_vm.py b/compute/compute/snippets/tests/test_create_vm.py index 5497f233bb1..fbda262ea25 100644 --- a/compute/compute/snippets/tests/test_create_vm.py +++ b/compute/compute/snippets/tests/test_create_vm.py @@ -17,6 +17,10 @@ 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, ) @@ -27,6 +31,7 @@ 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_snapshotted_data_disk import ( create_with_snapshotted_data_disk, ) @@ -108,89 +113,132 @@ def image(src_disk): wait_for_operation(op, PROJECT) -class TestCreation: - def test_create_from_custom_image(self, image): - instance_name = "i" + uuid.uuid4().hex[:10] - instance = create_from_custom_image( - PROJECT, INSTANCE_ZONE, instance_name, image.self_link +@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 ) - try: - assert ( - instance.disks[0].initialize_params.source_image == image.self_link - ) - finally: - delete_instance(PROJECT, INSTANCE_ZONE, instance_name) - - def test_create_from_public_image(self): - instance_name = "i" + uuid.uuid4().hex[:10] - instance = create_from_public_image( - PROJECT, - INSTANCE_ZONE, - instance_name, + assert any( + disk.disk_size_gb == 10 for disk in instance.disks ) - try: - assert "debian-cloud" in instance.disks[0].initialize_params.source_image - assert "debian-10" in instance.disks[0].initialize_params.source_image - finally: - delete_instance(PROJECT, INSTANCE_ZONE, instance_name) - - def test_create_from_snapshot(self, snapshot): - instance_name = "i" + uuid.uuid4().hex[:10] - instance = create_from_snapshot( - PROJECT, INSTANCE_ZONE, instance_name, snapshot.self_link + 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") ) - try: - assert ( - instance.disks[0].initialize_params.source_snapshot - == snapshot.self_link - ) - finally: - delete_instance(PROJECT, INSTANCE_ZONE, instance_name) - - def test_create_with_additional_disk(self): - instance_name = "i" + uuid.uuid4().hex[:10] - instance = create_with_additional_disk(PROJECT, INSTANCE_ZONE, instance_name) - try: - assert any( - disk.initialize_params.disk_size_gb == 20 for disk in instance.disks - ) - assert any( - disk.initialize_params.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(self, snapshot): - instance_name = "i" + uuid.uuid4().hex[:10] - instance = create_with_snapshotted_data_disk( - PROJECT, INSTANCE_ZONE, instance_name, snapshot.self_link + 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: + print(instance.disks) + for disk in instance.disks: + print(disk, dir(disk), type(disk), disk.disk_size_gb) + assert any( + disk.disk_size_gb == 13 for disk in instance.disks ) - try: - assert any( - disk.initialize_params.disk_size_gb == 11 for disk in instance.disks - ) - assert any( - disk.initialize_params.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(self): - instance_name = "i" + uuid.uuid4().hex[:10] - instance = create_with_subnet( - PROJECT, - INSTANCE_ZONE, - instance_name, - "global/networks/default", - f"regions/{REGION}/subnetworks/default", + assert any( + disk.disk_size_gb == 14 for disk in instance.disks ) - try: - assert instance.network_interfaces[0].name == "global/networks/default" - assert ( - instance.network_interfaces[0].subnetwork - == f"regions/{REGION}/subnetworks/default" - ) - finally: - delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + assert len(instance.disks) == 2 + finally: + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) From ccb1fece028f2c67098ea476ed8bdcf60de845c7 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Mon, 28 Mar 2022 18:03:18 +0200 Subject: [PATCH 073/113] chore(samples): Adding a test to make sure snippets are up-to-date (#243) * chore(samples): Adding a test to make sure snippets are up-to-date --- compute/compute/sgs.py | 1 + .../create_with_existing_disks.py | 4 +-- compute/compute/test_sgs.py | 25 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/compute/compute/sgs.py b/compute/compute/sgs.py index d8b2dbeb8d0..e3fa70a8b94 100644 --- a/compute/compute/sgs.py +++ b/compute/compute/sgs.py @@ -90,6 +90,7 @@ def __repr__(self): re.compile(r".*requirements-test\.txt$"), re.compile(r".*?/tests/.*"), re.compile(r".*?/__pycache__/.*"), + re.compile(r".*?sponge_log.xml.*"), ) diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py b/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py index 62a118ee850..df36012ea61 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py @@ -30,11 +30,11 @@ def get_disk(project_id: str, zone: str, disk_name: str) -> compute_v1.Disk: """ - Deletes a disk from a project. + 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 in which is the disk you want to delete. + zone: name of the zone where the disk exists. disk_name: name of the disk you want to retrieve. """ disk_client = compute_v1.DisksClient() diff --git a/compute/compute/test_sgs.py b/compute/compute/test_sgs.py index dcc030a17f2..fafae9ed3c7 100644 --- a/compute/compute/test_sgs.py +++ b/compute/compute/test_sgs.py @@ -16,6 +16,8 @@ from pathlib import Path import tempfile +import pytest + from . import sgs FIXTURE_INGREDIENTS = Path("sgs_test_fixtures/ingredients") @@ -30,3 +32,26 @@ def test_sgs_generate(): 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() From bfcda198f715221f30690fdd955764934978a31e Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Tue, 29 Mar 2022 01:02:11 +0000 Subject: [PATCH 074/113] chore(python): use black==22.3.0 (#249) Source-Link: https://github.com/googleapis/synthtool/commit/6fab84af09f2cf89a031fd8671d1def6b2931b11 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:7cffbc10910c3ab1b852c05114a08d374c195a81cdec1d4a67a1d129331d0bfe --- compute/compute/noxfile.py | 4 ++-- compute/compute/requirements.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compute/compute/noxfile.py b/compute/compute/noxfile.py index 85f5836dba3..25f87a215d4 100644 --- a/compute/compute/noxfile.py +++ b/compute/compute/noxfile.py @@ -29,7 +29,7 @@ # WARNING - WARNING - WARNING - WARNING - WARNING # WARNING - WARNING - WARNING - WARNING - WARNING -BLACK_VERSION = "black==19.10b0" +BLACK_VERSION = "black==22.3.0" # Copy `noxfile_config.py` to your directory and modify it instead. @@ -253,7 +253,7 @@ def py(session: nox.sessions.Session) -> None: def _get_repo_root() -> Optional[str]: - """ Returns the root folder of the project. """ + """Returns the root folder of the project.""" # Get root of this repository. Assume we don't have directories nested deeper than 10 items. p = Path(os.getcwd()) for i in range(10): diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index c0be0f7c63c..a6a43c7f828 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 -black==22.1.0 -google-cloud-compute==1.1.0 \ No newline at end of file +black==22.3.0 +google-cloud-compute==1.1.0 From 9730d6c5ce5cf8c2175bb94903df305924278849 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 8 Apr 2022 02:35:12 +0200 Subject: [PATCH 075/113] chore(deps): update dependency google-cloud-compute to v1.2.0 (#257) --- compute/compute/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index a6a43c7f828..f8dcc530c19 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 black==22.3.0 -google-cloud-compute==1.1.0 +google-cloud-compute==1.2.0 From efac50cd43590e01e745a01c5ca5aa3d1cc510f8 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 13 Apr 2022 16:31:00 +0200 Subject: [PATCH 076/113] docs(samples): Add samples for suspending/resuming an instance (#259) * chore(samples): Adding samples for suspend/resume * Fixing lint problems --- .../compute/ingredients/instances/resume.py | 52 ++++++++++++++ .../compute/ingredients/instances/suspend.py | 47 +++++++++++++ compute/compute/recipes/instances/resume.py | 21 ++++++ compute/compute/recipes/instances/suspend.py | 21 ++++++ compute/compute/snippets/instances/resume.py | 60 ++++++++++++++++ compute/compute/snippets/instances/suspend.py | 51 ++++++++++++++ .../tests/test_instance_suspend_resume.py | 69 +++++++++++++++++++ 7 files changed, 321 insertions(+) create mode 100644 compute/compute/ingredients/instances/resume.py create mode 100644 compute/compute/ingredients/instances/suspend.py create mode 100644 compute/compute/recipes/instances/resume.py create mode 100644 compute/compute/recipes/instances/suspend.py create mode 100644 compute/compute/snippets/instances/resume.py create mode 100644 compute/compute/snippets/instances/suspend.py create mode 100644 compute/compute/snippets/tests/test_instance_suspend_resume.py diff --git a/compute/compute/ingredients/instances/resume.py b/compute/compute/ingredients/instances/resume.py new file mode 100644 index 00000000000..5762797b396 --- /dev/null +++ b/compute/compute/ingredients/instances/resume.py @@ -0,0 +1,52 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 your want to resume. + """ + instance_client = compute_v1.InstancesClient() + op_client = compute_v1.ZoneOperationsClient() + + 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.") + + op = instance_client.resume_unary( + project=project_id, zone=zone, instance=instance_name + ) + + start = time.time() + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait(operation=op.name, zone=zone, project=project_id) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() + return +# + diff --git a/compute/compute/ingredients/instances/suspend.py b/compute/compute/ingredients/instances/suspend.py new file mode 100644 index 00000000000..50550d11220 --- /dev/null +++ b/compute/compute/ingredients/instances/suspend.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 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 your want to suspend. + """ + instance_client = compute_v1.InstancesClient() + op_client = compute_v1.ZoneOperationsClient() + + op = instance_client.suspend_unary( + project=project_id, zone=zone, instance=instance_name + ) + + start = time.time() + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait(operation=op.name, zone=zone, project=project_id) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() + return +# + diff --git a/compute/compute/recipes/instances/resume.py b/compute/compute/recipes/instances/resume.py new file mode 100644 index 00000000000..0165ccb9a38 --- /dev/null +++ b/compute/compute/recipes/instances/resume.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/compute/recipes/instances/suspend.py b/compute/compute/recipes/instances/suspend.py new file mode 100644 index 00000000000..5f3baa0be14 --- /dev/null +++ b/compute/compute/recipes/instances/suspend.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/compute/snippets/instances/resume.py b/compute/compute/snippets/instances/resume.py new file mode 100644 index 00000000000..a55b2fafe93 --- /dev/null +++ b/compute/compute/snippets/instances/resume.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. +# 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 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 your want to resume. + """ + instance_client = compute_v1.InstancesClient() + op_client = compute_v1.ZoneOperationsClient() + + 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." + ) + + op = instance_client.resume_unary( + project=project_id, zone=zone, instance=instance_name + ) + + start = time.time() + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait(operation=op.name, zone=zone, project=project_id) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() + return + + +# [END compute_resume_instance] diff --git a/compute/compute/snippets/instances/suspend.py b/compute/compute/snippets/instances/suspend.py new file mode 100644 index 00000000000..4427176a03a --- /dev/null +++ b/compute/compute/snippets/instances/suspend.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 + + +# 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 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 your want to suspend. + """ + instance_client = compute_v1.InstancesClient() + op_client = compute_v1.ZoneOperationsClient() + + op = instance_client.suspend_unary( + project=project_id, zone=zone, instance=instance_name + ) + + start = time.time() + while op.status != compute_v1.Operation.Status.DONE: + op = op_client.wait(operation=op.name, zone=zone, project=project_id) + if time.time() - start >= 300: # 5 minutes + raise TimeoutError() + return + + +# [END compute_suspend_instance] diff --git a/compute/compute/snippets/tests/test_instance_suspend_resume.py b/compute/compute/snippets/tests/test_instance_suspend_resume.py new file mode 100644 index 00000000000..a6f2ca103f0 --- /dev/null +++ b/compute/compute/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 From bff198a262f0359410a3db212ae2113cbb491ad6 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Thu, 14 Apr 2022 01:55:56 +0200 Subject: [PATCH 077/113] chore(deps): update dependency google-cloud-storage to v2.3.0 (#262) --- compute/compute/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements-test.txt b/compute/compute/requirements-test.txt index 0d1f5b210dd..d40fd21e53a 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -1,4 +1,4 @@ pytest==7.1.1 pytest-parallel==0.1.1 flaky==3.7.0 -google-cloud-storage==2.2.1 +google-cloud-storage==2.3.0 From de2fa19ad176eb9940005bbf7706fcfe6eb7dd25 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Wed, 20 Apr 2022 21:19:37 -0400 Subject: [PATCH 078/113] chore(python): add nox session to sort python imports (#266) Source-Link: https://github.com/googleapis/synthtool/commit/1b71c10e20de7ed3f97f692f99a0e3399b67049f Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:00c9d764fd1cd56265f12a5ef4b99a0c9e87cf261018099141e2ca5158890416 Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- compute/compute/noxfile.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/compute/compute/noxfile.py b/compute/compute/noxfile.py index 25f87a215d4..a40410b5636 100644 --- a/compute/compute/noxfile.py +++ b/compute/compute/noxfile.py @@ -30,6 +30,7 @@ # WARNING - WARNING - WARNING - WARNING - WARNING BLACK_VERSION = "black==22.3.0" +ISORT_VERSION = "isort==5.10.1" # Copy `noxfile_config.py` to your directory and modify it instead. @@ -168,12 +169,33 @@ def lint(session: nox.sessions.Session) -> None: @nox.session def blacken(session: nox.sessions.Session) -> None: + """Run black. Format code to uniform standard.""" session.install(BLACK_VERSION) python_files = [path for path in os.listdir(".") if path.endswith(".py")] session.run("black", *python_files) +# +# format = isort + black +# + + +@nox.session +def format(session: nox.sessions.Session) -> None: + """ + Run isort to sort imports. Then run black + to format code to uniform standard. + """ + session.install(BLACK_VERSION, ISORT_VERSION) + python_files = [path for path in os.listdir(".") if path.endswith(".py")] + + # Use the --fss option to sort imports using strict alphabetical order. + # See https://pycqa.github.io/isort/docs/configuration/options.html#force-sort-within-sections + session.run("isort", "--fss", *python_files) + session.run("black", *python_files) + + # # Sample Tests # From e9c5ce9ca8393bbb7149c4d22856af0e6709e4ac Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Thu, 21 Apr 2022 16:46:53 +0200 Subject: [PATCH 079/113] docs(samples): Migrate samples to use new type of operations (#264) Co-authored-by: Anthonios Partheniou --- .../ingredients/disks/autodelete_change.py | 14 +-- .../ingredients/disks/create_empty_disk.py | 14 +-- .../ingredients/disks/create_from_image.py | 12 +- .../ingredients/disks/create_from_snapshot.py | 13 +- compute/compute/ingredients/disks/delete.py | 13 +- .../compute/ingredients/firewall/create.py | 6 +- .../compute/ingredients/firewall/delete.py | 6 +- compute/compute/ingredients/firewall/patch.py | 6 +- .../ingredients/instance-templates/create.py | 7 +- .../create_from_instance.py | 6 +- .../instance-templates/create_with_subnet.py | 5 +- .../ingredients/instance-templates/delete.py | 5 +- .../ingredients/instances/create_instance.py | 23 +--- .../create_instance_from_template.py | 5 +- ...e_instance_from_template_with_overrides.py | 5 +- .../custom_machine_types/update_memory.py | 9 +- .../compute/ingredients/instances/delete.py | 16 +-- .../instances/delete_protection/set.py | 5 +- .../compute/ingredients/instances/reset.py | 10 +- .../compute/ingredients/instances/resume.py | 9 +- .../compute/ingredients/instances/start.py | 9 +- .../ingredients/instances/start_encrypted.py | 9 +- compute/compute/ingredients/instances/stop.py | 10 +- .../compute/ingredients/instances/suspend.py | 9 +- .../operations/handle_extended_operation.py | 68 +++++++++++ .../operations/list_zone_operations.py | 2 +- .../compute/ingredients/snapshots/create.py | 15 +-- .../compute/ingredients/snapshots/delete.py | 15 +-- .../ingredients/usage_report/disable.py | 7 +- .../ingredients/usage_report/set_bucket.py | 7 +- .../recipes/disks/autodelete_change.py | 2 + .../recipes/disks/create_empty_disk.py | 2 + .../recipes/disks/create_from_image.py | 2 + .../recipes/disks/create_from_snapshot.py | 2 + compute/compute/recipes/disks/delete.py | 2 + compute/compute/recipes/firewall/create.py | 2 + compute/compute/recipes/firewall/delete.py | 2 + compute/compute/recipes/firewall/patch.py | 2 + .../recipes/instance_templates/create.py | 2 + .../create_from_instance.py | 2 + .../instance_templates/create_with_subnet.py | 2 + .../recipes/instance_templates/delete.py | 2 + compute/compute/recipes/instances/create.py | 2 + .../create_from_custom_image.py | 2 + .../create_from_public_image.py | 2 + .../create_from_snapshot.py | 2 + .../create_with_additional_disk.py | 2 + .../create_with_existing_disks.py | 2 + .../create_with_snapshotted_data_disk.py | 2 + .../recipes/instances/create_with_subnet.py | 2 + .../instances/custom_hostname/create.py | 2 + .../create_shared_with_helper.py | 2 + .../create_with_helper.py | 2 + .../create_without_helper.py | 2 + .../extra_mem_no_helper.py | 2 + .../custom_machine_types/update_memory.py | 2 + compute/compute/recipes/instances/delete.py | 2 + .../instances/delete_protection/create.py | 2 + .../instances/delete_protection/set.py | 2 + .../create_from_template.py | 2 + .../create_from_template_with_overrides.py | 2 + .../preemptible/create_preemptible.py | 2 + compute/compute/recipes/instances/reset.py | 1 + compute/compute/recipes/instances/resume.py | 2 + compute/compute/recipes/instances/start.py | 2 + .../recipes/instances/start_encrypted.py | 2 + compute/compute/recipes/instances/stop.py | 2 + compute/compute/recipes/instances/suspend.py | 2 + compute/compute/recipes/snapshots/create.py | 2 + compute/compute/recipes/snapshots/delete.py | 2 + .../recipes/usage_report/usage_reports.py | 6 + .../snippets/disks/autodelete_change.py | 63 ++++++++-- .../snippets/disks/create_empty_disk.py | 66 +++++++--- .../snippets/disks/create_from_image.py | 66 +++++++--- .../snippets/disks/create_from_snapshot.py | 65 +++++++--- compute/compute/snippets/disks/delete.py | 64 +++++++--- compute/compute/snippets/firewall/create.py | 55 ++++++++- compute/compute/snippets/firewall/delete.py | 57 ++++++++- compute/compute/snippets/firewall/main.py | 17 +-- compute/compute/snippets/firewall/patch.py | 55 ++++++++- .../snippets/instance_templates/create.py | 56 ++++++++- .../create_from_instance.py | 56 ++++++++- .../instance_templates/create_with_subnet.py | 55 ++++++++- .../snippets/instance_templates/delete.py | 55 ++++++++- compute/compute/snippets/instances/create.py | 68 ++++++++--- .../create_from_custom_image.py | 68 ++++++++--- .../create_from_public_image.py | 68 ++++++++--- .../create_from_snapshot.py | 68 ++++++++--- .../create_with_additional_disk.py | 68 ++++++++--- .../create_with_existing_disks.py | 68 ++++++++--- .../create_with_snapshotted_data_disk.py | 68 ++++++++--- .../snippets/instances/create_with_subnet.py | 68 ++++++++--- .../instances/custom_hostname/create.py | 68 ++++++++--- .../create_shared_with_helper.py | 68 ++++++++--- .../create_with_helper.py | 68 ++++++++--- .../create_without_helper.py | 68 ++++++++--- .../extra_mem_no_helper.py | 68 ++++++++--- .../custom_machine_types/update_memory.py | 58 ++++++++- compute/compute/snippets/instances/delete.py | 64 +++++++--- .../instances/delete_protection/create.py | 68 ++++++++--- .../instances/delete_protection/set.py | 55 ++++++++- .../create_from_template.py | 55 ++++++++- .../create_from_template_with_overrides.py | 55 ++++++++- .../preemptible/create_preemptible.py | 68 ++++++++--- compute/compute/snippets/instances/reset.py | 59 +++++++-- compute/compute/snippets/instances/resume.py | 58 +++++++-- compute/compute/snippets/instances/start.py | 58 +++++++-- .../snippets/instances/start_encrypted.py | 58 +++++++-- compute/compute/snippets/instances/stop.py | 59 +++++++-- compute/compute/snippets/instances/suspend.py | 58 +++++++-- compute/compute/snippets/snapshots/create.py | 64 ++++++++-- compute/compute/snippets/snapshots/delete.py | 61 ++++++++-- .../compute/snippets/tests/test_create_vm.py | 3 - .../snippets/usage_report/usage_reports.py | 114 ++++++++++++++++-- 114 files changed, 2402 insertions(+), 652 deletions(-) create mode 100644 compute/compute/ingredients/operations/handle_extended_operation.py diff --git a/compute/compute/ingredients/disks/autodelete_change.py b/compute/compute/ingredients/disks/autodelete_change.py index 589fe75bf37..4238c30949c 100644 --- a/compute/compute/ingredients/disks/autodelete_change.py +++ b/compute/compute/ingredients/disks/autodelete_change.py @@ -46,16 +46,8 @@ def set_disk_autodelete(project_id: str, zone: str, instance_name: str, disk_nam disk.auto_delete = autodelete - operation = instance_client.update_unary(project=project_id, zone=zone, instance=instance_name, instance_resource=instance) - operation_client = compute_v1.ZoneOperationsClient() - operation = operation_client.wait(project=project_id, zone=zone, operation=operation.name) - - if operation.error: - print("Error during instance update:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warnings during instance update:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + 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/compute/ingredients/disks/create_empty_disk.py b/compute/compute/ingredients/disks/create_empty_disk.py index 7422caf45e6..b06f283f08f 100644 --- a/compute/compute/ingredients/disks/create_empty_disk.py +++ b/compute/compute/ingredients/disks/create_empty_disk.py @@ -47,17 +47,9 @@ def create_empty_disk( disk.type_ = disk_type disk_client = compute_v1.DisksClient() - operation = disk_client.insert_unary(project=project_id, zone=zone, disk_resource=disk) - operation_client = compute_v1.ZoneOperationsClient() - operation = operation_client.wait(project=project_id, zone=zone, operation=operation.name) - - if operation.error: - print("Error during disk creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warnings during disk creation:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + 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/compute/ingredients/disks/create_from_image.py b/compute/compute/ingredients/disks/create_from_image.py index c8e1bdce148..ab6ebf28ecc 100644 --- a/compute/compute/ingredients/disks/create_from_image.py +++ b/compute/compute/ingredients/disks/create_from_image.py @@ -51,17 +51,9 @@ def create_disk_from_image( disk.source_image = source_image disk_client = compute_v1.DisksClient() - operation = disk_client.insert_unary(project=project_id, zone=zone, disk_resource=disk) - operation_client = compute_v1.ZoneOperationsClient() - operation = operation_client.wait(project=project_id, zone=zone, operation=operation.name) + operation = disk_client.insert(project=project_id, zone=zone, disk_resource=disk) - if operation.error: - print("Error during disk creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warnings during disk creation:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + wait_for_extended_operation(operation, "disk creation") return disk_client.get(project=project_id, zone=zone, disk=disk.name) # diff --git a/compute/compute/ingredients/disks/create_from_snapshot.py b/compute/compute/ingredients/disks/create_from_snapshot.py index 059ab01801d..88a79751204 100644 --- a/compute/compute/ingredients/disks/create_from_snapshot.py +++ b/compute/compute/ingredients/disks/create_from_snapshot.py @@ -48,18 +48,9 @@ def create_disk_from_snapshot(project_id: str, zone: str, disk_name: str, disk_t disk.source_snapshot = snapshot_link disk.type_ = disk_type disk.name = disk_name - operation = disk_client.insert_unary(project=project_id, zone=zone, disk_resource=disk) - operation_client = compute_v1.ZoneOperationsClient() - operation = operation_client.wait(project=project_id, zone=zone, operation=operation.name) + operation = disk_client.insert(project=project_id, zone=zone, disk_resource=disk) - if operation.error: - print("Error during disk creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - - if operation.warnings: - print("Warnings during disk creation:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + wait_for_extended_operation(operation, "disk creation") return disk_client.get(project=project_id, zone=zone, disk=disk_name) # diff --git a/compute/compute/ingredients/disks/delete.py b/compute/compute/ingredients/disks/delete.py index d66294eceb0..6b3788b9415 100644 --- a/compute/compute/ingredients/disks/delete.py +++ b/compute/compute/ingredients/disks/delete.py @@ -33,16 +33,7 @@ def delete_disk(project_id: str, zone: str, disk_name: str) -> NoReturn: disk_name: name of the disk you want to delete. """ disk_client = compute_v1.DisksClient() - operation = disk_client.delete_unary(project=project_id, zone=zone, disk=disk_name) - operation_client = compute_v1.ZoneOperationsClient() - operation = operation_client.wait(project=project_id, zone=zone, operation=operation.name) - - if operation.error: - print("Error during disk delete operation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warnings during disk delete operation:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + operation = disk_client.delete(project=project_id, zone=zone, disk=disk_name) + wait_for_extended_operation(operation, "disk deletion") return # diff --git a/compute/compute/ingredients/firewall/create.py b/compute/compute/ingredients/firewall/create.py index e6e9f000818..d6f17b090a4 100644 --- a/compute/compute/ingredients/firewall/create.py +++ b/compute/compute/ingredients/firewall/create.py @@ -16,6 +16,7 @@ # 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 @@ -61,12 +62,11 @@ def create_firewall_rule( # firewall_rule.priority = 0 firewall_client = compute_v1.FirewallsClient() - op = firewall_client.insert_unary( + operation = firewall_client.insert( project=project_id, firewall_resource=firewall_rule ) - op_client = compute_v1.GlobalOperationsClient() - op_client.wait(project=project_id, operation=op.name) + wait_for_extended_operation(operation, "firewall rule creation") return firewall_client.get(project=project_id, firewall=firewall_rule_name) # diff --git a/compute/compute/ingredients/firewall/delete.py b/compute/compute/ingredients/firewall/delete.py index fc6a42150fa..1f4b8703106 100644 --- a/compute/compute/ingredients/firewall/delete.py +++ b/compute/compute/ingredients/firewall/delete.py @@ -16,6 +16,7 @@ # 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 @@ -29,11 +30,10 @@ def delete_firewall_rule(project_id: str, firewall_rule_name: str) -> None: firewall_rule_name: name of the firewall rule you want to delete. """ firewall_client = compute_v1.FirewallsClient() - operation = firewall_client.delete_unary( + operation = firewall_client.delete( project=project_id, firewall=firewall_rule_name ) - operation_client = compute_v1.GlobalOperationsClient() - operation_client.wait(project=project_id, operation=operation.name) + wait_for_extended_operation(operation, "firewall rule deletion") return # diff --git a/compute/compute/ingredients/firewall/patch.py b/compute/compute/ingredients/firewall/patch.py index 5017114a33d..0c44979cd3b 100644 --- a/compute/compute/ingredients/firewall/patch.py +++ b/compute/compute/ingredients/firewall/patch.py @@ -16,6 +16,7 @@ # 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 @@ -35,12 +36,11 @@ def patch_firewall_priority(project_id: str, firewall_rule_name: str, 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_unary( + operation = firewall_client.patch( project=project_id, firewall=firewall_rule_name, firewall_resource=firewall_rule ) - operation_client = compute_v1.GlobalOperationsClient() - operation_client.wait(project=project_id, operation=operation.name) + wait_for_extended_operation(operation, "firewall rule patching") return # diff --git a/compute/compute/ingredients/instance-templates/create.py b/compute/compute/ingredients/instance-templates/create.py index ca56e99a84f..9c79f8c76b7 100644 --- a/compute/compute/ingredients/instance-templates/create.py +++ b/compute/compute/ingredients/instance-templates/create.py @@ -16,6 +16,7 @@ # 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 @@ -64,11 +65,11 @@ def create_template(project_id: str, template_name: str) -> compute_v1.InstanceT template.properties.network_interfaces = [network_interface] template_client = compute_v1.InstanceTemplatesClient() - operation_client = compute_v1.GlobalOperationsClient() - op = template_client.insert_unary( + operation = template_client.insert( project=project_id, instance_template_resource=template ) - operation_client.wait(project=project_id, operation=op.name) + + wait_for_extended_operation(operation, "instance template creation") return template_client.get(project=project_id, instance_template=template_name) # diff --git a/compute/compute/ingredients/instance-templates/create_from_instance.py b/compute/compute/ingredients/instance-templates/create_from_instance.py index 584e2f17724..1450cf02f21 100644 --- a/compute/compute/ingredients/instance-templates/create_from_instance.py +++ b/compute/compute/ingredients/instance-templates/create_from_instance.py @@ -54,11 +54,11 @@ def create_template_from_instance( template.source_instance_params.disk_configs = [disk] template_client = compute_v1.InstanceTemplatesClient() - operation_client = compute_v1.GlobalOperationsClient() - op = template_client.insert_unary( + operation = template_client.insert( project=project_id, instance_template_resource=template ) - operation_client.wait(project=project_id, operation=op.name) + + wait_for_extended_operation(operation, "instance template creation") return template_client.get(project=project_id, instance_template=template_name) # diff --git a/compute/compute/ingredients/instance-templates/create_with_subnet.py b/compute/compute/ingredients/instance-templates/create_with_subnet.py index fb80d2510a2..fd91bb3c977 100644 --- a/compute/compute/ingredients/instance-templates/create_with_subnet.py +++ b/compute/compute/ingredients/instance-templates/create_with_subnet.py @@ -63,11 +63,10 @@ def create_template_with_subnet( template.properties.network_interfaces = [network_interface] template_client = compute_v1.InstanceTemplatesClient() - operation_client = compute_v1.GlobalOperationsClient() - op = template_client.insert_unary( + operation = template_client.insert( project=project_id, instance_template_resource=template ) - operation_client.wait(project=project_id, operation=op.name) + wait_for_extended_operation(operation, "instance template creation") return template_client.get(project=project_id, instance_template=template_name) # diff --git a/compute/compute/ingredients/instance-templates/delete.py b/compute/compute/ingredients/instance-templates/delete.py index 23bd929eeec..f3720666026 100644 --- a/compute/compute/ingredients/instance-templates/delete.py +++ b/compute/compute/ingredients/instance-templates/delete.py @@ -30,10 +30,9 @@ def delete_instance_template(project_id: str, template_name: str): template_name: name of the template to delete. """ template_client = compute_v1.InstanceTemplatesClient() - operation_client = compute_v1.GlobalOperationsClient() - op = template_client.delete_unary( + operation = template_client.delete( project=project_id, instance_template=template_name ) - operation_client.wait(project=project_id, operation=op.name) + wait_for_extended_operation(operation, "instance template deletion") return # diff --git a/compute/compute/ingredients/instances/create_instance.py b/compute/compute/ingredients/instances/create_instance.py index 17ab8319b13..f49fa6a981a 100644 --- a/compute/compute/ingredients/instances/create_instance.py +++ b/compute/compute/ingredients/instances/create_instance.py @@ -18,11 +18,10 @@ # flake8: noqa import re -import sys -from google.cloud import compute_v1 -import time from typing import List +from google.cloud import compute_v1 + # def create_instance( @@ -79,7 +78,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -135,19 +133,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/ingredients/instances/create_instance_from_template.py b/compute/compute/ingredients/instances/create_instance_from_template.py index 73ff814f4bb..b007f4f4023 100644 --- a/compute/compute/ingredients/instances/create_instance_from_template.py +++ b/compute/compute/ingredients/instances/create_instance_from_template.py @@ -41,7 +41,6 @@ def create_instance_from_template( Returns: Instance object. """ - operation_client = compute_v1.ZoneOperationsClient() instance_client = compute_v1.InstancesClient() instance_insert_request = compute_v1.InsertInstanceRequest() @@ -50,8 +49,8 @@ def create_instance_from_template( instance_insert_request.source_instance_template = instance_template_url instance_insert_request.instance_resource.name = instance_name - op = instance_client.insert_unary(instance_insert_request) - operation_client.wait(project=project_id, zone=zone, operation=op.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/compute/ingredients/instances/create_instance_from_template_with_overrides.py b/compute/compute/ingredients/instances/create_instance_from_template_with_overrides.py index 001cb5179b5..5920a06482a 100644 --- a/compute/compute/ingredients/instances/create_instance_from_template_with_overrides.py +++ b/compute/compute/ingredients/instances/create_instance_from_template_with_overrides.py @@ -53,7 +53,6 @@ def create_instance_from_template_with_overrides( Returns: Instance object. """ - operation_client = compute_v1.ZoneOperationsClient() instance_client = compute_v1.InstancesClient() instance_template_client = compute_v1.InstanceTemplatesClient() @@ -90,8 +89,8 @@ def create_instance_from_template_with_overrides( instance_insert_request.instance_resource = instance instance_insert_request.source_instance_template = instance_template.self_link - op = instance_client.insert_unary(instance_insert_request) - operation_client.wait(project=project_id, zone=zone, operation=op.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) # \ No newline at end of file diff --git a/compute/compute/ingredients/instances/custom_machine_types/update_memory.py b/compute/compute/ingredients/instances/custom_machine_types/update_memory.py index ee9ed2a75f2..fd1afac8912 100644 --- a/compute/compute/ingredients/instances/custom_machine_types/update_memory.py +++ b/compute/compute/ingredients/instances/custom_machine_types/update_memory.py @@ -38,7 +38,6 @@ def add_extended_memory_to_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() instance = instance_client.get( project=project_id, zone=zone, instance=instance_name ) @@ -51,10 +50,10 @@ def add_extended_memory_to_instance( instance.Status.TERMINATED.name, instance.Status.STOPPED.name, ): - op = instance_client.stop_unary( + operation = instance_client.stop( project=project_id, zone=zone, instance=instance_name ) - operation_client.wait(project=project_id, zone=zone, operation=op.name) + wait_for_extended_operation(operation, "instance stopping") start = time.time() while instance.status not in ( instance.Status.TERMINATED.name, @@ -77,13 +76,13 @@ def add_extended_memory_to_instance( # cmt.memory_mb = new_memory # cmt.extra_memory_used = True # instance.machine_type = str(cmt) - op = instance_client.update_unary( + operation = instance_client.update( project=project_id, zone=zone, instance=instance_name, instance_resource=instance, ) - operation_client.wait(project=project_id, zone=zone, operation=op.name) + wait_for_extended_operation(operation, "instance update") return instance_client.get(project=project_id, zone=zone, instance=instance_name) # diff --git a/compute/compute/ingredients/instances/delete.py b/compute/compute/ingredients/instances/delete.py index ee84a349c34..bd16d439ae9 100644 --- a/compute/compute/ingredients/instances/delete.py +++ b/compute/compute/ingredients/instances/delete.py @@ -33,24 +33,12 @@ def delete_instance(project_id: str, zone: str, machine_name: str) -> None: machine_name: name of the machine you want to delete. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() print(f"Deleting {machine_name} from {zone}...") - operation = instance_client.delete_unary( + operation = instance_client.delete( project=project_id, zone=zone, instance=machine_name ) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during deletion:", operation.error, file=sys.stderr) - return - if operation.warnings: - print("Warning during deletion:", operation.warnings, file=sys.stderr) + wait_for_extended_operation(operation, "instance deletion") print(f"Instance {machine_name} deleted.") return # diff --git a/compute/compute/ingredients/instances/delete_protection/set.py b/compute/compute/ingredients/instances/delete_protection/set.py index fd7bd4ca919..4d3a73a5be7 100644 --- a/compute/compute/ingredients/instances/delete_protection/set.py +++ b/compute/compute/ingredients/instances/delete_protection/set.py @@ -33,7 +33,6 @@ def set_delete_protection( protected against deletion or not. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() request = compute_v1.SetDeletionProtectionInstanceRequest() request.project = project_id @@ -41,7 +40,7 @@ def set_delete_protection( request.resource = instance_name request.deletion_protection = delete_protection - operation = instance_client.set_deletion_protection_unary(request) - operation_client.wait(project=project_id, zone=zone, operation=operation.name) + operation = instance_client.set_deletion_protection(request) + wait_for_extended_operation(operation, "changing delete protection setting") return # diff --git a/compute/compute/ingredients/instances/reset.py b/compute/compute/ingredients/instances/reset.py index a0d29a1d9a3..f784e4b91dc 100644 --- a/compute/compute/ingredients/instances/reset.py +++ b/compute/compute/ingredients/instances/reset.py @@ -31,16 +31,12 @@ def reset_instance(project_id: str, zone: str, instance_name: str) -> None: instance_name: name of the instance your want to reset. """ instance_client = compute_v1.InstancesClient() - op_client = compute_v1.ZoneOperationsClient() - op = instance_client.reset_unary( + operation = instance_client.reset( project=project_id, zone=zone, instance=instance_name ) - start = time.time() - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() + wait_for_extended_operation(operation, "instance reset") + return # diff --git a/compute/compute/ingredients/instances/resume.py b/compute/compute/ingredients/instances/resume.py index 5762797b396..2dcfcd325e3 100644 --- a/compute/compute/ingredients/instances/resume.py +++ b/compute/compute/ingredients/instances/resume.py @@ -31,22 +31,17 @@ def resume_instance(project_id: str, zone: str, instance_name: str) -> None: instance_name: name of the instance your want to resume. """ instance_client = compute_v1.InstancesClient() - op_client = compute_v1.ZoneOperationsClient() 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.") - op = instance_client.resume_unary( + operation = instance_client.resume( project=project_id, zone=zone, instance=instance_name ) - start = time.time() - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() + wait_for_extended_operation(operation, "instance resumption") return # diff --git a/compute/compute/ingredients/instances/start.py b/compute/compute/ingredients/instances/start.py index a57359b103b..eeba32eb77c 100644 --- a/compute/compute/ingredients/instances/start.py +++ b/compute/compute/ingredients/instances/start.py @@ -31,16 +31,11 @@ def start_instance(project_id: str, zone: str, instance_name: str) -> None: instance_name: name of the instance your want to start. """ instance_client = compute_v1.InstancesClient() - op_client = compute_v1.ZoneOperationsClient() - op = instance_client.start_unary( + operation = instance_client.start( project=project_id, zone=zone, instance=instance_name ) - start = time.time() - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() + wait_for_extended_operation(operation, "instance start") return # diff --git a/compute/compute/ingredients/instances/start_encrypted.py b/compute/compute/ingredients/instances/start_encrypted.py index e90c56f2a8b..201d2233d36 100644 --- a/compute/compute/ingredients/instances/start_encrypted.py +++ b/compute/compute/ingredients/instances/start_encrypted.py @@ -36,7 +36,6 @@ def start_instance_with_encryption_key( https://cloud.google.com/compute/docs/disks/customer-supplied-encryption#specifications """ instance_client = compute_v1.InstancesClient() - op_client = compute_v1.ZoneOperationsClient() instance_data = instance_client.get( project=project_id, zone=zone, instance=instance_name @@ -52,17 +51,13 @@ def start_instance_with_encryption_key( enc_data = compute_v1.InstancesStartWithEncryptionKeyRequest() enc_data.disks = [disk_data] - op = instance_client.start_with_encryption_key_unary( + operation = instance_client.start_with_encryption_key( project=project_id, zone=zone, instance=instance_name, instances_start_with_encryption_key_request_resource=enc_data, ) - start = time.time() - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() + wait_for_extended_operation(operation, "instance start (with encrypted disk)") return # diff --git a/compute/compute/ingredients/instances/stop.py b/compute/compute/ingredients/instances/stop.py index 903053348bc..ea1b532f89e 100644 --- a/compute/compute/ingredients/instances/stop.py +++ b/compute/compute/ingredients/instances/stop.py @@ -31,16 +31,10 @@ def stop_instance(project_id: str, zone: str, instance_name: str) -> None: instance_name: name of the instance your want to stop. """ instance_client = compute_v1.InstancesClient() - op_client = compute_v1.ZoneOperationsClient() - op = instance_client.stop_unary( + operation = instance_client.stop( project=project_id, zone=zone, instance=instance_name ) - - start = time.time() - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() + wait_for_extended_operation(operation, "instance stopping") return # diff --git a/compute/compute/ingredients/instances/suspend.py b/compute/compute/ingredients/instances/suspend.py index 50550d11220..d38cac81e3c 100644 --- a/compute/compute/ingredients/instances/suspend.py +++ b/compute/compute/ingredients/instances/suspend.py @@ -31,17 +31,12 @@ def suspend_instance(project_id: str, zone: str, instance_name: str) -> None: instance_name: name of the instance your want to suspend. """ instance_client = compute_v1.InstancesClient() - op_client = compute_v1.ZoneOperationsClient() - op = instance_client.suspend_unary( + operation = instance_client.suspend( project=project_id, zone=zone, instance=instance_name ) - start = time.time() - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() + wait_for_extended_operation(operation, "instance suspend") return # diff --git a/compute/compute/ingredients/operations/handle_extended_operation.py b/compute/compute/ingredients/operations/handle_extended_operation.py new file mode 100644 index 00000000000..7c75c17f7aa --- /dev/null +++ b/compute/compute/ingredients/operations/handle_extended_operation.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 +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) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result +# diff --git a/compute/compute/ingredients/operations/list_zone_operations.py b/compute/compute/ingredients/operations/list_zone_operations.py index 7089f023f8d..472e893d5a2 100644 --- a/compute/compute/ingredients/operations/list_zone_operations.py +++ b/compute/compute/ingredients/operations/list_zone_operations.py @@ -43,4 +43,4 @@ def list_zone_operations( request.filter = filter return operation_client.list(request) -# \ No newline at end of file +# diff --git a/compute/compute/ingredients/snapshots/create.py b/compute/compute/ingredients/snapshots/create.py index 3e3b2a97cfe..1c876a206bb 100644 --- a/compute/compute/ingredients/snapshots/create.py +++ b/compute/compute/ingredients/snapshots/create.py @@ -16,7 +16,6 @@ # 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 @@ -43,17 +42,9 @@ def create_snapshot(project_id: str, zone: str, disk_name: str, snapshot_name: s snapshot.name = snapshot_name snapshot_client = compute_v1.SnapshotsClient() - operation = snapshot_client.insert_unary(project=project_id, snapshot_resource=snapshot) - op_client = compute_v1.GlobalOperationsClient() - operation = op_client.wait(project=project_id, operation=operation.name) - - if operation.error: - print("Error during snapshot creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warnings during snapshot creation:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + 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/compute/ingredients/snapshots/delete.py b/compute/compute/ingredients/snapshots/delete.py index dd3f1f29c15..e7f5af9bc12 100644 --- a/compute/compute/ingredients/snapshots/delete.py +++ b/compute/compute/ingredients/snapshots/delete.py @@ -16,7 +16,6 @@ # 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 @@ -33,17 +32,9 @@ def delete_snapshot(project_id: str, snapshot_name: str) -> NoReturn: """ snapshot_client = compute_v1.SnapshotsClient() - operation = snapshot_client.delete_unary(project=project_id, snapshot=snapshot_name) - op_client = compute_v1.GlobalOperationsClient() - operation = op_client.wait(project=project_id, operation=operation.name) - - if operation.error: - print("Error during snapshot deletion:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warnings during snapshot deletion:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + operation = snapshot_client.delete(project=project_id, snapshot=snapshot_name) + + wait_for_extended_operation(operation, "snapshot deletion") return # diff --git a/compute/compute/ingredients/usage_report/disable.py b/compute/compute/ingredients/usage_report/disable.py index e8d1464cbca..4fc7ad6c142 100644 --- a/compute/compute/ingredients/usage_report/disable.py +++ b/compute/compute/ingredients/usage_report/disable.py @@ -31,13 +31,10 @@ def disable_usage_export(project_id: str) -> None: # Setting `usage_export_location_resource` to an # empty object will disable the usage report generation. - operation = projects_client.set_usage_export_bucket_unary( + operation = projects_client.set_usage_export_bucket( project=project_id, usage_export_location_resource={} ) - op_client = compute_v1.GlobalOperationsClient() - - while operation.status != compute_v1.Operation.Status.DONE: - operation = op_client.wait(operation=operation.name, project=project_id) + wait_for_extended_operation(operation, "disabling GCE usage bucket") # diff --git a/compute/compute/ingredients/usage_report/set_bucket.py b/compute/compute/ingredients/usage_report/set_bucket.py index 7a948c8b706..f308257df33 100644 --- a/compute/compute/ingredients/usage_report/set_bucket.py +++ b/compute/compute/ingredients/usage_report/set_bucket.py @@ -49,13 +49,10 @@ def set_usage_export_bucket( ) projects_client = compute_v1.ProjectsClient() - operation = projects_client.set_usage_export_bucket_unary( + operation = projects_client.set_usage_export_bucket( project=project_id, usage_export_location_resource=usage_export_location ) - op_client = compute_v1.GlobalOperationsClient() - - while operation.status != compute_v1.Operation.Status.DONE: - operation = op_client.wait(operation=operation.name, project=project_id) + wait_for_extended_operation(operation, "setting GCE usage bucket") # diff --git a/compute/compute/recipes/disks/autodelete_change.py b/compute/compute/recipes/disks/autodelete_change.py index 33120f89775..62465ac913f 100644 --- a/compute/compute/recipes/disks/autodelete_change.py +++ b/compute/compute/recipes/disks/autodelete_change.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/disks/create_empty_disk.py b/compute/compute/recipes/disks/create_empty_disk.py index 923f79d076c..c044d56f326 100644 --- a/compute/compute/recipes/disks/create_empty_disk.py +++ b/compute/compute/recipes/disks/create_empty_disk.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/disks/create_from_image.py b/compute/compute/recipes/disks/create_from_image.py index 407b4aa0d72..a1aa318af70 100644 --- a/compute/compute/recipes/disks/create_from_image.py +++ b/compute/compute/recipes/disks/create_from_image.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/disks/create_from_snapshot.py b/compute/compute/recipes/disks/create_from_snapshot.py index c33060ca2cd..efc3e29cc62 100644 --- a/compute/compute/recipes/disks/create_from_snapshot.py +++ b/compute/compute/recipes/disks/create_from_snapshot.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/disks/delete.py b/compute/compute/recipes/disks/delete.py index e319a075c85..c8bf1d904b3 100644 --- a/compute/compute/recipes/disks/delete.py +++ b/compute/compute/recipes/disks/delete.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/firewall/create.py b/compute/compute/recipes/firewall/create.py index 8d76598fa1d..b4770f78a72 100644 --- a/compute/compute/recipes/firewall/create.py +++ b/compute/compute/recipes/firewall/create.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/firewall/delete.py b/compute/compute/recipes/firewall/delete.py index 6c3752fa643..ed25d5d1fe0 100644 --- a/compute/compute/recipes/firewall/delete.py +++ b/compute/compute/recipes/firewall/delete.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/firewall/patch.py b/compute/compute/recipes/firewall/patch.py index 543157e1ac1..48b7173089e 100644 --- a/compute/compute/recipes/firewall/patch.py +++ b/compute/compute/recipes/firewall/patch.py @@ -16,5 +16,7 @@ # # +# + # # diff --git a/compute/compute/recipes/instance_templates/create.py b/compute/compute/recipes/instance_templates/create.py index 6c313c2d28e..f3fe9d7971f 100644 --- a/compute/compute/recipes/instance_templates/create.py +++ b/compute/compute/recipes/instance_templates/create.py @@ -16,5 +16,7 @@ # # +# + # # diff --git a/compute/compute/recipes/instance_templates/create_from_instance.py b/compute/compute/recipes/instance_templates/create_from_instance.py index 751416fe397..2059793f321 100644 --- a/compute/compute/recipes/instance_templates/create_from_instance.py +++ b/compute/compute/recipes/instance_templates/create_from_instance.py @@ -16,5 +16,7 @@ # # +# + # # diff --git a/compute/compute/recipes/instance_templates/create_with_subnet.py b/compute/compute/recipes/instance_templates/create_with_subnet.py index 85639db006b..0aeba05dd1f 100644 --- a/compute/compute/recipes/instance_templates/create_with_subnet.py +++ b/compute/compute/recipes/instance_templates/create_with_subnet.py @@ -16,5 +16,7 @@ # # +# + # # diff --git a/compute/compute/recipes/instance_templates/delete.py b/compute/compute/recipes/instance_templates/delete.py index bf774c57d63..8a9743b613b 100644 --- a/compute/compute/recipes/instance_templates/delete.py +++ b/compute/compute/recipes/instance_templates/delete.py @@ -16,5 +16,7 @@ # # +# + # # diff --git a/compute/compute/recipes/instances/create.py b/compute/compute/recipes/instances/create.py index b51a2e73779..3f35a1e4c5a 100644 --- a/compute/compute/recipes/instances/create.py +++ b/compute/compute/recipes/instances/create.py @@ -20,6 +20,8 @@ # +# + # # diff --git a/compute/compute/recipes/instances/create_start_instance/create_from_custom_image.py b/compute/compute/recipes/instances/create_start_instance/create_from_custom_image.py index e50d60367af..a093f8cb31c 100644 --- a/compute/compute/recipes/instances/create_start_instance/create_from_custom_image.py +++ b/compute/compute/recipes/instances/create_start_instance/create_from_custom_image.py @@ -21,6 +21,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/create_start_instance/create_from_public_image.py b/compute/compute/recipes/instances/create_start_instance/create_from_public_image.py index 6f6f0ee042c..ac6f2d9bdfb 100644 --- a/compute/compute/recipes/instances/create_start_instance/create_from_public_image.py +++ b/compute/compute/recipes/instances/create_start_instance/create_from_public_image.py @@ -21,6 +21,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/create_start_instance/create_from_snapshot.py b/compute/compute/recipes/instances/create_start_instance/create_from_snapshot.py index 2047eeb57bd..34d3e9a9bab 100644 --- a/compute/compute/recipes/instances/create_start_instance/create_from_snapshot.py +++ b/compute/compute/recipes/instances/create_start_instance/create_from_snapshot.py @@ -18,6 +18,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/create_start_instance/create_with_additional_disk.py b/compute/compute/recipes/instances/create_start_instance/create_with_additional_disk.py index ab9baa6e4c8..097cc1cf244 100644 --- a/compute/compute/recipes/instances/create_start_instance/create_with_additional_disk.py +++ b/compute/compute/recipes/instances/create_start_instance/create_with_additional_disk.py @@ -25,6 +25,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/create_start_instance/create_with_existing_disks.py b/compute/compute/recipes/instances/create_start_instance/create_with_existing_disks.py index 3f6bf6ac20f..dfa59eb3646 100644 --- a/compute/compute/recipes/instances/create_start_instance/create_with_existing_disks.py +++ b/compute/compute/recipes/instances/create_start_instance/create_with_existing_disks.py @@ -19,6 +19,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.py b/compute/compute/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.py index 858e6188422..19aec72dc13 100644 --- a/compute/compute/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.py +++ b/compute/compute/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.py @@ -25,6 +25,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/create_with_subnet.py b/compute/compute/recipes/instances/create_with_subnet.py index 906edca50d7..989e6448360 100644 --- a/compute/compute/recipes/instances/create_with_subnet.py +++ b/compute/compute/recipes/instances/create_with_subnet.py @@ -21,6 +21,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/custom_hostname/create.py b/compute/compute/recipes/instances/custom_hostname/create.py index 55f3b47e679..588c603827b 100644 --- a/compute/compute/recipes/instances/custom_hostname/create.py +++ b/compute/compute/recipes/instances/custom_hostname/create.py @@ -21,6 +21,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/custom_machine_types/create_shared_with_helper.py b/compute/compute/recipes/instances/custom_machine_types/create_shared_with_helper.py index 6adc8009821..a4d355d4dd8 100644 --- a/compute/compute/recipes/instances/custom_machine_types/create_shared_with_helper.py +++ b/compute/compute/recipes/instances/custom_machine_types/create_shared_with_helper.py @@ -24,6 +24,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/custom_machine_types/create_with_helper.py b/compute/compute/recipes/instances/custom_machine_types/create_with_helper.py index 0ea883cf5a5..8619e68227f 100644 --- a/compute/compute/recipes/instances/custom_machine_types/create_with_helper.py +++ b/compute/compute/recipes/instances/custom_machine_types/create_with_helper.py @@ -25,6 +25,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/custom_machine_types/create_without_helper.py b/compute/compute/recipes/instances/custom_machine_types/create_without_helper.py index e88388a82d2..6b99722f296 100644 --- a/compute/compute/recipes/instances/custom_machine_types/create_without_helper.py +++ b/compute/compute/recipes/instances/custom_machine_types/create_without_helper.py @@ -21,6 +21,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/custom_machine_types/extra_mem_no_helper.py b/compute/compute/recipes/instances/custom_machine_types/extra_mem_no_helper.py index 68fdc27590b..3b998965d70 100644 --- a/compute/compute/recipes/instances/custom_machine_types/extra_mem_no_helper.py +++ b/compute/compute/recipes/instances/custom_machine_types/extra_mem_no_helper.py @@ -21,6 +21,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/custom_machine_types/update_memory.py b/compute/compute/recipes/instances/custom_machine_types/update_memory.py index 5817cd987dd..4d6b2ced889 100644 --- a/compute/compute/recipes/instances/custom_machine_types/update_memory.py +++ b/compute/compute/recipes/instances/custom_machine_types/update_memory.py @@ -16,5 +16,7 @@ # # +# + # # diff --git a/compute/compute/recipes/instances/delete.py b/compute/compute/recipes/instances/delete.py index 68fc7f5546b..99cff9e259c 100644 --- a/compute/compute/recipes/instances/delete.py +++ b/compute/compute/recipes/instances/delete.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/instances/delete_protection/create.py b/compute/compute/recipes/instances/delete_protection/create.py index f1bb3a9b39a..c45d757eae1 100644 --- a/compute/compute/recipes/instances/delete_protection/create.py +++ b/compute/compute/recipes/instances/delete_protection/create.py @@ -21,6 +21,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/delete_protection/set.py b/compute/compute/recipes/instances/delete_protection/set.py index 785e8f7813f..ebd5b5970a3 100644 --- a/compute/compute/recipes/instances/delete_protection/set.py +++ b/compute/compute/recipes/instances/delete_protection/set.py @@ -16,5 +16,7 @@ # # +# + # # diff --git a/compute/compute/recipes/instances/from_instance_template/create_from_template.py b/compute/compute/recipes/instances/from_instance_template/create_from_template.py index c296366cc90..9a07cd10c36 100644 --- a/compute/compute/recipes/instances/from_instance_template/create_from_template.py +++ b/compute/compute/recipes/instances/from_instance_template/create_from_template.py @@ -16,5 +16,7 @@ # # +# + # # diff --git a/compute/compute/recipes/instances/from_instance_template/create_from_template_with_overrides.py b/compute/compute/recipes/instances/from_instance_template/create_from_template_with_overrides.py index 27d1b2ae080..c4d5ca1094f 100644 --- a/compute/compute/recipes/instances/from_instance_template/create_from_template_with_overrides.py +++ b/compute/compute/recipes/instances/from_instance_template/create_from_template_with_overrides.py @@ -16,5 +16,7 @@ # # +# + # # diff --git a/compute/compute/recipes/instances/preemptible/create_preemptible.py b/compute/compute/recipes/instances/preemptible/create_preemptible.py index a6161541211..18791d35d5e 100644 --- a/compute/compute/recipes/instances/preemptible/create_preemptible.py +++ b/compute/compute/recipes/instances/preemptible/create_preemptible.py @@ -21,6 +21,8 @@ # +# + # diff --git a/compute/compute/recipes/instances/reset.py b/compute/compute/recipes/instances/reset.py index 0842ed544b5..ce09f66e79b 100644 --- a/compute/compute/recipes/instances/reset.py +++ b/compute/compute/recipes/instances/reset.py @@ -16,6 +16,7 @@ # # +# # # diff --git a/compute/compute/recipes/instances/resume.py b/compute/compute/recipes/instances/resume.py index 0165ccb9a38..4cc8d7ef5d9 100644 --- a/compute/compute/recipes/instances/resume.py +++ b/compute/compute/recipes/instances/resume.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/instances/start.py b/compute/compute/recipes/instances/start.py index 9ea6be08a88..913680aa9fa 100644 --- a/compute/compute/recipes/instances/start.py +++ b/compute/compute/recipes/instances/start.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/instances/start_encrypted.py b/compute/compute/recipes/instances/start_encrypted.py index 6833c644eff..d6a0194bd5a 100644 --- a/compute/compute/recipes/instances/start_encrypted.py +++ b/compute/compute/recipes/instances/start_encrypted.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/instances/stop.py b/compute/compute/recipes/instances/stop.py index 7dda8bcfab0..49abe79a036 100644 --- a/compute/compute/recipes/instances/stop.py +++ b/compute/compute/recipes/instances/stop.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/instances/suspend.py b/compute/compute/recipes/instances/suspend.py index 5f3baa0be14..59c5b75f091 100644 --- a/compute/compute/recipes/instances/suspend.py +++ b/compute/compute/recipes/instances/suspend.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/snapshots/create.py b/compute/compute/recipes/snapshots/create.py index b77eb7a9832..5b54951b1d5 100644 --- a/compute/compute/recipes/snapshots/create.py +++ b/compute/compute/recipes/snapshots/create.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/snapshots/delete.py b/compute/compute/recipes/snapshots/delete.py index ff8e89c8e07..a9686d6d3c3 100644 --- a/compute/compute/recipes/snapshots/delete.py +++ b/compute/compute/recipes/snapshots/delete.py @@ -16,6 +16,8 @@ # # +# + # # diff --git a/compute/compute/recipes/usage_report/usage_reports.py b/compute/compute/recipes/usage_report/usage_reports.py index 4a293b8007b..d153b48d9e3 100644 --- a/compute/compute/recipes/usage_report/usage_reports.py +++ b/compute/compute/recipes/usage_report/usage_reports.py @@ -28,6 +28,9 @@ # + +# + # # @@ -40,6 +43,9 @@ # + +# + # # diff --git a/compute/compute/snippets/disks/autodelete_change.py b/compute/compute/snippets/disks/autodelete_change.py index 943564cd181..197e275d67f 100644 --- a/compute/compute/snippets/disks/autodelete_change.py +++ b/compute/compute/snippets/disks/autodelete_change.py @@ -21,11 +21,58 @@ # [START compute_disk_autodelete_change] import sys -from typing import NoReturn +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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def set_disk_autodelete( project_id: str, zone: str, instance_name: str, disk_name: str, autodelete: bool ) -> NoReturn: @@ -54,24 +101,14 @@ def set_disk_autodelete( disk.auto_delete = autodelete - operation = instance_client.update_unary( + operation = instance_client.update( project=project_id, zone=zone, instance=instance_name, instance_resource=instance, ) - operation_client = compute_v1.ZoneOperationsClient() - operation = operation_client.wait( - project=project_id, zone=zone, operation=operation.name - ) - if operation.error: - print("Error during instance update:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warnings during instance update:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + wait_for_extended_operation(operation, "disk update") return diff --git a/compute/compute/snippets/disks/create_empty_disk.py b/compute/compute/snippets/disks/create_empty_disk.py index d1352f861ed..ff86b77d02c 100644 --- a/compute/compute/snippets/disks/create_empty_disk.py +++ b/compute/compute/snippets/disks/create_empty_disk.py @@ -21,10 +21,58 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_empty_disk( project_id: str, zone: str, disk_name: str, disk_type: str, disk_size_gb: int ) -> compute_v1.Disk: @@ -50,21 +98,9 @@ def create_empty_disk( disk.type_ = disk_type disk_client = compute_v1.DisksClient() - operation = disk_client.insert_unary( - project=project_id, zone=zone, disk_resource=disk - ) - operation_client = compute_v1.ZoneOperationsClient() - operation = operation_client.wait( - project=project_id, zone=zone, operation=operation.name - ) - - if operation.error: - print("Error during disk creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warnings during disk creation:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + 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/compute/snippets/disks/create_from_image.py b/compute/compute/snippets/disks/create_from_image.py index 74e998ada27..51ccb8e8cc5 100644 --- a/compute/compute/snippets/disks/create_from_image.py +++ b/compute/compute/snippets/disks/create_from_image.py @@ -21,10 +21,58 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_disk_from_image( project_id: str, zone: str, @@ -59,21 +107,9 @@ def create_disk_from_image( disk.source_image = source_image disk_client = compute_v1.DisksClient() - operation = disk_client.insert_unary( - project=project_id, zone=zone, disk_resource=disk - ) - operation_client = compute_v1.ZoneOperationsClient() - operation = operation_client.wait( - project=project_id, zone=zone, operation=operation.name - ) - - if operation.error: - print("Error during disk creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warnings during disk creation:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + 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/compute/snippets/disks/create_from_snapshot.py b/compute/compute/snippets/disks/create_from_snapshot.py index 2421adb0f8e..8aef45adeff 100644 --- a/compute/compute/snippets/disks/create_from_snapshot.py +++ b/compute/compute/snippets/disks/create_from_snapshot.py @@ -21,10 +21,58 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_disk_from_snapshot( project_id: str, zone: str, @@ -57,22 +105,9 @@ def create_disk_from_snapshot( disk.source_snapshot = snapshot_link disk.type_ = disk_type disk.name = disk_name - operation = disk_client.insert_unary( - project=project_id, zone=zone, disk_resource=disk - ) - operation_client = compute_v1.ZoneOperationsClient() - operation = operation_client.wait( - project=project_id, zone=zone, operation=operation.name - ) - - if operation.error: - print("Error during disk creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) + operation = disk_client.insert(project=project_id, zone=zone, disk_resource=disk) - if operation.warnings: - print("Warnings during disk creation:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + wait_for_extended_operation(operation, "disk creation") return disk_client.get(project=project_id, zone=zone, disk=disk_name) diff --git a/compute/compute/snippets/disks/delete.py b/compute/compute/snippets/disks/delete.py index 0b5ea3cb39a..f5c21d6fe80 100644 --- a/compute/compute/snippets/disks/delete.py +++ b/compute/compute/snippets/disks/delete.py @@ -21,11 +21,58 @@ # [START compute_disk_delete] import sys -from typing import NoReturn +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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def delete_disk(project_id: str, zone: str, disk_name: str) -> NoReturn: """ Deletes a disk from a project. @@ -36,19 +83,8 @@ def delete_disk(project_id: str, zone: str, disk_name: str) -> NoReturn: disk_name: name of the disk you want to delete. """ disk_client = compute_v1.DisksClient() - operation = disk_client.delete_unary(project=project_id, zone=zone, disk=disk_name) - operation_client = compute_v1.ZoneOperationsClient() - operation = operation_client.wait( - project=project_id, zone=zone, operation=operation.name - ) - - if operation.error: - print("Error during disk delete operation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warnings during disk delete operation:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + operation = disk_client.delete(project=project_id, zone=zone, disk=disk_name) + wait_for_extended_operation(operation, "disk deletion") return diff --git a/compute/compute/snippets/firewall/create.py b/compute/compute/snippets/firewall/create.py index 1eb230dea75..8d80a08d8b2 100644 --- a/compute/compute/snippets/firewall/create.py +++ b/compute/compute/snippets/firewall/create.py @@ -20,9 +20,59 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_firewall_rule( project_id: str, firewall_rule_name: str, network: str = "global/networks/default" ) -> compute_v1.Firewall: @@ -64,12 +114,11 @@ def create_firewall_rule( # firewall_rule.priority = 0 firewall_client = compute_v1.FirewallsClient() - op = firewall_client.insert_unary( + operation = firewall_client.insert( project=project_id, firewall_resource=firewall_rule ) - op_client = compute_v1.GlobalOperationsClient() - op_client.wait(project=project_id, operation=op.name) + wait_for_extended_operation(operation, "firewall rule creation") return firewall_client.get(project=project_id, firewall=firewall_rule_name) diff --git a/compute/compute/snippets/firewall/delete.py b/compute/compute/snippets/firewall/delete.py index d606912a518..fe2c7c08ea5 100644 --- a/compute/compute/snippets/firewall/delete.py +++ b/compute/compute/snippets/firewall/delete.py @@ -20,9 +20,59 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def delete_firewall_rule(project_id: str, firewall_rule_name: str) -> None: """ Deletes a firewall rule from the project. @@ -32,12 +82,9 @@ def delete_firewall_rule(project_id: str, firewall_rule_name: str) -> None: firewall_rule_name: name of the firewall rule you want to delete. """ firewall_client = compute_v1.FirewallsClient() - operation = firewall_client.delete_unary( - project=project_id, firewall=firewall_rule_name - ) + operation = firewall_client.delete(project=project_id, firewall=firewall_rule_name) - operation_client = compute_v1.GlobalOperationsClient() - operation_client.wait(project=project_id, operation=operation.name) + wait_for_extended_operation(operation, "firewall rule deletion") return diff --git a/compute/compute/snippets/firewall/main.py b/compute/compute/snippets/firewall/main.py index b53e677e07f..b506328df3b 100644 --- a/compute/compute/snippets/firewall/main.py +++ b/compute/compute/snippets/firewall/main.py @@ -65,12 +65,11 @@ def create_firewall_rule( # firewall_rule.priority = 0 firewall_client = compute_v1.FirewallsClient() - op = firewall_client.insert_unary( + operation = firewall_client.insert( project=project_id, firewall_resource=firewall_rule ) - op_client = compute_v1.GlobalOperationsClient() - op_client.wait(project=project_id, operation=op.name) + wait_for_extended_operation(operation, "firewall rule creation") return firewall_client.get(project=project_id, firewall=firewall_rule_name) @@ -84,12 +83,9 @@ def delete_firewall_rule(project_id: str, firewall_rule_name: str) -> None: firewall_rule_name: name of the firewall rule you want to delete. """ firewall_client = compute_v1.FirewallsClient() - operation = firewall_client.delete_unary( - project=project_id, firewall=firewall_rule_name - ) + operation = firewall_client.delete(project=project_id, firewall=firewall_rule_name) - operation_client = compute_v1.GlobalOperationsClient() - operation_client.wait(project=project_id, operation=operation.name) + wait_for_extended_operation(operation, "firewall rule deletion") return @@ -145,12 +141,11 @@ def patch_firewall_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_unary( + operation = firewall_client.patch( project=project_id, firewall=firewall_rule_name, firewall_resource=firewall_rule ) - operation_client = compute_v1.GlobalOperationsClient() - operation_client.wait(project=project_id, operation=operation.name) + wait_for_extended_operation(operation, "firewall rule patching") return diff --git a/compute/compute/snippets/firewall/patch.py b/compute/compute/snippets/firewall/patch.py index 9dbb823da3f..e81624dc908 100644 --- a/compute/compute/snippets/firewall/patch.py +++ b/compute/compute/snippets/firewall/patch.py @@ -20,9 +20,59 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def patch_firewall_priority( project_id: str, firewall_rule_name: str, priority: int ) -> None: @@ -40,12 +90,11 @@ def patch_firewall_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_unary( + operation = firewall_client.patch( project=project_id, firewall=firewall_rule_name, firewall_resource=firewall_rule ) - operation_client = compute_v1.GlobalOperationsClient() - operation_client.wait(project=project_id, operation=operation.name) + wait_for_extended_operation(operation, "firewall rule patching") return diff --git a/compute/compute/snippets/instance_templates/create.py b/compute/compute/snippets/instance_templates/create.py index f328bbfc130..4b63601c89c 100644 --- a/compute/compute/snippets/instance_templates/create.py +++ b/compute/compute/snippets/instance_templates/create.py @@ -20,9 +20,59 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + 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 @@ -66,11 +116,11 @@ def create_template(project_id: str, template_name: str) -> compute_v1.InstanceT template.properties.network_interfaces = [network_interface] template_client = compute_v1.InstanceTemplatesClient() - operation_client = compute_v1.GlobalOperationsClient() - op = template_client.insert_unary( + operation = template_client.insert( project=project_id, instance_template_resource=template ) - operation_client.wait(project=project_id, operation=op.name) + + wait_for_extended_operation(operation, "instance template creation") return template_client.get(project=project_id, instance_template=template_name) diff --git a/compute/compute/snippets/instance_templates/create_from_instance.py b/compute/compute/snippets/instance_templates/create_from_instance.py index 20afc715435..7ceff5074e6 100644 --- a/compute/compute/snippets/instance_templates/create_from_instance.py +++ b/compute/compute/snippets/instance_templates/create_from_instance.py @@ -20,9 +20,59 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_template_from_instance( project_id: str, instance: str, template_name: str ) -> compute_v1.InstanceTemplate: @@ -56,11 +106,11 @@ def create_template_from_instance( template.source_instance_params.disk_configs = [disk] template_client = compute_v1.InstanceTemplatesClient() - operation_client = compute_v1.GlobalOperationsClient() - op = template_client.insert_unary( + operation = template_client.insert( project=project_id, instance_template_resource=template ) - operation_client.wait(project=project_id, operation=op.name) + + wait_for_extended_operation(operation, "instance template creation") return template_client.get(project=project_id, instance_template=template_name) diff --git a/compute/compute/snippets/instance_templates/create_with_subnet.py b/compute/compute/snippets/instance_templates/create_with_subnet.py index ea6ddc19166..922a0ec27a9 100644 --- a/compute/compute/snippets/instance_templates/create_with_subnet.py +++ b/compute/compute/snippets/instance_templates/create_with_subnet.py @@ -20,9 +20,59 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_template_with_subnet( project_id: str, network: str, subnetwork: str, template_name: str ) -> compute_v1.InstanceTemplate: @@ -65,11 +115,10 @@ def create_template_with_subnet( template.properties.network_interfaces = [network_interface] template_client = compute_v1.InstanceTemplatesClient() - operation_client = compute_v1.GlobalOperationsClient() - op = template_client.insert_unary( + operation = template_client.insert( project=project_id, instance_template_resource=template ) - operation_client.wait(project=project_id, operation=op.name) + wait_for_extended_operation(operation, "instance template creation") return template_client.get(project=project_id, instance_template=template_name) diff --git a/compute/compute/snippets/instance_templates/delete.py b/compute/compute/snippets/instance_templates/delete.py index b0700c9abdb..d024f70c978 100644 --- a/compute/compute/snippets/instance_templates/delete.py +++ b/compute/compute/snippets/instance_templates/delete.py @@ -20,9 +20,59 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def delete_instance_template(project_id: str, template_name: str): """ Delete an instance template. @@ -32,11 +82,10 @@ def delete_instance_template(project_id: str, template_name: str): template_name: name of the template to delete. """ template_client = compute_v1.InstanceTemplatesClient() - operation_client = compute_v1.GlobalOperationsClient() - op = template_client.delete_unary( + operation = template_client.delete( project=project_id, instance_template=template_name ) - operation_client.wait(project=project_id, operation=op.name) + wait_for_extended_operation(operation, "instance template deletion") return diff --git a/compute/compute/snippets/instances/create.py b/compute/compute/snippets/instances/create.py index efa0de1735a..512ed6176d9 100644 --- a/compute/compute/snippets/instances/create.py +++ b/compute/compute/snippets/instances/create.py @@ -22,9 +22,9 @@ # [START compute_instances_create] import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -83,6 +83,52 @@ def disk_from_image( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -137,7 +183,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -193,19 +238,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/create_start_instance/create_from_custom_image.py b/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py index 59d32f08c14..16dfa1b86d4 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py @@ -22,9 +22,9 @@ # [START compute_instances_create_from_custom_image] import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -83,6 +83,52 @@ def disk_from_image( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -137,7 +183,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -193,19 +238,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/create_start_instance/create_from_public_image.py b/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py index bfe848afaf8..881aa2c85f3 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py @@ -22,9 +22,9 @@ # [START compute_instances_create_from_image] import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -83,6 +83,52 @@ def disk_from_image( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -137,7 +183,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -193,19 +238,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/create_start_instance/create_from_snapshot.py b/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py index 2c6996ed89a..f7397b0adc4 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py @@ -22,9 +22,9 @@ # [START compute_instances_create_from_snapshot] import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -65,6 +65,52 @@ def disk_from_snapshot( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -119,7 +165,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -175,19 +220,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/create_start_instance/create_with_additional_disk.py b/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py index a14005eb5d9..b506a9f0912 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py @@ -22,9 +22,9 @@ # [START compute_instances_create_from_image_plus_empty_disk] import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -113,6 +113,52 @@ def empty_disk( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -167,7 +213,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -223,19 +268,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/create_start_instance/create_with_existing_disks.py b/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py index df36012ea61..121582d9126 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py @@ -22,9 +22,9 @@ # [START compute_instances_create_with_existing_disks] import re import sys -import time -from typing import Iterable, List, NoReturn +from typing import Any, Iterable, List, NoReturn +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -41,6 +41,52 @@ def get_disk(project_id: str, zone: str, disk_name: str) -> compute_v1.Disk: 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -95,7 +141,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -151,19 +196,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py b/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py index 0385c17abc4..dbffbb174ee 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py @@ -22,9 +22,9 @@ # [START compute_instances_create_from_image_plus_snapshot_disk] import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -120,6 +120,52 @@ def disk_from_snapshot( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -174,7 +220,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -230,19 +275,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/create_with_subnet.py b/compute/compute/snippets/instances/create_with_subnet.py index 27a52a9f75c..3c28b84707e 100644 --- a/compute/compute/snippets/instances/create_with_subnet.py +++ b/compute/compute/snippets/instances/create_with_subnet.py @@ -22,9 +22,9 @@ # [START compute_instances_create_with_subnet] import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -83,6 +83,52 @@ def disk_from_image( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -137,7 +183,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -193,19 +238,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/custom_hostname/create.py b/compute/compute/snippets/instances/custom_hostname/create.py index 699f6397e75..377a3169f51 100644 --- a/compute/compute/snippets/instances/custom_hostname/create.py +++ b/compute/compute/snippets/instances/custom_hostname/create.py @@ -22,9 +22,9 @@ # [START compute_instances_create_custom_hostname] import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -83,6 +83,52 @@ def disk_from_image( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -137,7 +183,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -193,19 +238,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py index 7674013d44e..4f37ae2a256 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py @@ -25,9 +25,9 @@ from enum import unique import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -277,6 +277,52 @@ def disk_from_image( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -331,7 +377,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -387,19 +432,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/custom_machine_types/create_with_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py index 5821f160b59..499c6305955 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py @@ -25,9 +25,9 @@ from enum import unique import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -277,6 +277,52 @@ def disk_from_image( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -331,7 +377,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -387,19 +432,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/custom_machine_types/create_without_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py index 54e208b6df7..d7e13d8c16d 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py @@ -22,9 +22,9 @@ # [START compute_custom_machine_type_create_without_helper] import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -83,6 +83,52 @@ def disk_from_image( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -137,7 +183,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -193,19 +238,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py b/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py index 9449ef11f51..05975032833 100644 --- a/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py @@ -22,9 +22,9 @@ # [START compute_custom_machine_type_extra_mem_no_helper] import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -83,6 +83,52 @@ def disk_from_image( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -137,7 +183,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -193,19 +238,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/custom_machine_types/update_memory.py b/compute/compute/snippets/instances/custom_machine_types/update_memory.py index b65c9434402..b61a2a2b570 100644 --- a/compute/compute/snippets/instances/custom_machine_types/update_memory.py +++ b/compute/compute/snippets/instances/custom_machine_types/update_memory.py @@ -20,11 +20,60 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def add_extended_memory_to_instance( project_id: str, zone: str, instance_name: str, new_memory: int ): @@ -41,7 +90,6 @@ def add_extended_memory_to_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() instance = instance_client.get( project=project_id, zone=zone, instance=instance_name ) @@ -58,10 +106,10 @@ def add_extended_memory_to_instance( instance.Status.TERMINATED.name, instance.Status.STOPPED.name, ): - op = instance_client.stop_unary( + operation = instance_client.stop( project=project_id, zone=zone, instance=instance_name ) - operation_client.wait(project=project_id, zone=zone, operation=op.name) + wait_for_extended_operation(operation, "instance stopping") start = time.time() while instance.status not in ( instance.Status.TERMINATED.name, @@ -84,13 +132,13 @@ def add_extended_memory_to_instance( # cmt.memory_mb = new_memory # cmt.extra_memory_used = True # instance.machine_type = str(cmt) - op = instance_client.update_unary( + operation = instance_client.update( project=project_id, zone=zone, instance=instance_name, instance_resource=instance, ) - operation_client.wait(project=project_id, zone=zone, operation=op.name) + wait_for_extended_operation(operation, "instance update") return instance_client.get(project=project_id, zone=zone, instance=instance_name) diff --git a/compute/compute/snippets/instances/delete.py b/compute/compute/snippets/instances/delete.py index 55cdfe9c7d8..af847455836 100644 --- a/compute/compute/snippets/instances/delete.py +++ b/compute/compute/snippets/instances/delete.py @@ -22,10 +22,58 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + 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. @@ -36,24 +84,12 @@ def delete_instance(project_id: str, zone: str, machine_name: str) -> None: machine_name: name of the machine you want to delete. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() print(f"Deleting {machine_name} from {zone}...") - operation = instance_client.delete_unary( + operation = instance_client.delete( project=project_id, zone=zone, instance=machine_name ) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during deletion:", operation.error, file=sys.stderr) - return - if operation.warnings: - print("Warning during deletion:", operation.warnings, file=sys.stderr) + wait_for_extended_operation(operation, "instance deletion") print(f"Instance {machine_name} deleted.") return diff --git a/compute/compute/snippets/instances/delete_protection/create.py b/compute/compute/snippets/instances/delete_protection/create.py index 15d31f102a8..fe9d26e60e3 100644 --- a/compute/compute/snippets/instances/delete_protection/create.py +++ b/compute/compute/snippets/instances/delete_protection/create.py @@ -22,9 +22,9 @@ # [START compute_delete_protection_create] import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -83,6 +83,52 @@ def disk_from_image( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -137,7 +183,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -193,19 +238,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/delete_protection/set.py b/compute/compute/snippets/instances/delete_protection/set.py index 47c3b35ba22..3a60083066e 100644 --- a/compute/compute/snippets/instances/delete_protection/set.py +++ b/compute/compute/snippets/instances/delete_protection/set.py @@ -20,9 +20,59 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def set_delete_protection( project_id: str, zone: str, instance_name: str, delete_protection: bool ) -> None: @@ -36,7 +86,6 @@ def set_delete_protection( protected against deletion or not. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() request = compute_v1.SetDeletionProtectionInstanceRequest() request.project = project_id @@ -44,8 +93,8 @@ def set_delete_protection( request.resource = instance_name request.deletion_protection = delete_protection - operation = instance_client.set_deletion_protection_unary(request) - operation_client.wait(project=project_id, zone=zone, operation=operation.name) + operation = instance_client.set_deletion_protection(request) + wait_for_extended_operation(operation, "changing delete protection setting") return diff --git a/compute/compute/snippets/instances/from_instance_template/create_from_template.py b/compute/compute/snippets/instances/from_instance_template/create_from_template.py index 6310cfd112c..3abd6cc469e 100644 --- a/compute/compute/snippets/instances/from_instance_template/create_from_template.py +++ b/compute/compute/snippets/instances/from_instance_template/create_from_template.py @@ -20,9 +20,59 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance_from_template( project_id: str, zone: str, instance_name: str, instance_template_url: str ) -> compute_v1.Instance: @@ -43,7 +93,6 @@ def create_instance_from_template( Returns: Instance object. """ - operation_client = compute_v1.ZoneOperationsClient() instance_client = compute_v1.InstancesClient() instance_insert_request = compute_v1.InsertInstanceRequest() @@ -52,8 +101,8 @@ def create_instance_from_template( instance_insert_request.source_instance_template = instance_template_url instance_insert_request.instance_resource.name = instance_name - op = instance_client.insert_unary(instance_insert_request) - operation_client.wait(project=project_id, zone=zone, operation=op.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/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py b/compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py index 6f76d32900e..eca6f0a49ca 100644 --- a/compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py +++ b/compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py @@ -20,9 +20,59 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance_from_template_with_overrides( project_id: str, zone: str, @@ -55,7 +105,6 @@ def create_instance_from_template_with_overrides( Returns: Instance object. """ - operation_client = compute_v1.ZoneOperationsClient() instance_client = compute_v1.InstancesClient() instance_template_client = compute_v1.InstanceTemplatesClient() @@ -92,8 +141,8 @@ def create_instance_from_template_with_overrides( instance_insert_request.instance_resource = instance instance_insert_request.source_instance_template = instance_template.self_link - op = instance_client.insert_unary(instance_insert_request) - operation_client.wait(project=project_id, zone=zone, operation=op.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/compute/snippets/instances/preemptible/create_preemptible.py b/compute/compute/snippets/instances/preemptible/create_preemptible.py index 5cc7ff47d4a..3e2ef4b897a 100644 --- a/compute/compute/snippets/instances/preemptible/create_preemptible.py +++ b/compute/compute/snippets/instances/preemptible/create_preemptible.py @@ -22,9 +22,9 @@ # [START compute_preemptible_create] import re import sys -import time -from typing import List +from typing import Any, List +from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -83,6 +83,52 @@ def disk_from_image( 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_instance( project_id: str, zone: str, @@ -137,7 +183,6 @@ def create_instance( Instance object. """ instance_client = compute_v1.InstancesClient() - operation_client = compute_v1.ZoneOperationsClient() # Use the network interface provided in the network_link argument. network_interface = compute_v1.NetworkInterface() @@ -193,19 +238,10 @@ def create_instance( # Wait for the create operation to complete. print(f"Creating the {instance_name} instance in {zone}...") - operation = instance_client.insert_unary(request=request) - start = time.time() - while operation.status != compute_v1.Operation.Status.DONE: - operation = operation_client.wait( - operation=operation.name, zone=zone, project=project_id - ) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() - if operation.error: - print("Error during creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warning during creation:", operation.warnings, file=sys.stderr) + 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/compute/snippets/instances/reset.py b/compute/compute/snippets/instances/reset.py index a04003f17bc..1764ab632c1 100644 --- a/compute/compute/snippets/instances/reset.py +++ b/compute/compute/snippets/instances/reset.py @@ -20,11 +20,60 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def reset_instance(project_id: str, zone: str, instance_name: str) -> None: """ Resets a stopped Google Compute Engine instance (with unencrypted disks). @@ -34,17 +83,13 @@ def reset_instance(project_id: str, zone: str, instance_name: str) -> None: instance_name: name of the instance your want to reset. """ instance_client = compute_v1.InstancesClient() - op_client = compute_v1.ZoneOperationsClient() - op = instance_client.reset_unary( + operation = instance_client.reset( project=project_id, zone=zone, instance=instance_name ) - start = time.time() - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() + wait_for_extended_operation(operation, "instance reset") + return diff --git a/compute/compute/snippets/instances/resume.py b/compute/compute/snippets/instances/resume.py index a55b2fafe93..2c6cf68cbf2 100644 --- a/compute/compute/snippets/instances/resume.py +++ b/compute/compute/snippets/instances/resume.py @@ -20,11 +20,60 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def resume_instance(project_id: str, zone: str, instance_name: str) -> None: """ Resume a suspended Google Compute Engine instance (with unencrypted disks). @@ -34,7 +83,6 @@ def resume_instance(project_id: str, zone: str, instance_name: str) -> None: instance_name: name of the instance your want to resume. """ instance_client = compute_v1.InstancesClient() - op_client = compute_v1.ZoneOperationsClient() instance = instance_client.get( project=project_id, zone=zone, instance=instance_name @@ -45,15 +93,11 @@ def resume_instance(project_id: str, zone: str, instance_name: str) -> None: f"Instance {instance_name} is in {instance.status} state." ) - op = instance_client.resume_unary( + operation = instance_client.resume( project=project_id, zone=zone, instance=instance_name ) - start = time.time() - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() + wait_for_extended_operation(operation, "instance resumption") return diff --git a/compute/compute/snippets/instances/start.py b/compute/compute/snippets/instances/start.py index 4548a8cd9bb..9dbf039aee2 100644 --- a/compute/compute/snippets/instances/start.py +++ b/compute/compute/snippets/instances/start.py @@ -20,11 +20,60 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def start_instance(project_id: str, zone: str, instance_name: str) -> None: """ Starts a stopped Google Compute Engine instance (with unencrypted disks). @@ -34,17 +83,12 @@ def start_instance(project_id: str, zone: str, instance_name: str) -> None: instance_name: name of the instance your want to start. """ instance_client = compute_v1.InstancesClient() - op_client = compute_v1.ZoneOperationsClient() - op = instance_client.start_unary( + operation = instance_client.start( project=project_id, zone=zone, instance=instance_name ) - start = time.time() - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() + wait_for_extended_operation(operation, "instance start") return diff --git a/compute/compute/snippets/instances/start_encrypted.py b/compute/compute/snippets/instances/start_encrypted.py index 401b2b0cc9a..ef08fb1dd63 100644 --- a/compute/compute/snippets/instances/start_encrypted.py +++ b/compute/compute/snippets/instances/start_encrypted.py @@ -20,11 +20,60 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def start_instance_with_encryption_key( project_id: str, zone: str, instance_name: str, key: bytes ): @@ -39,7 +88,6 @@ def start_instance_with_encryption_key( https://cloud.google.com/compute/docs/disks/customer-supplied-encryption#specifications """ instance_client = compute_v1.InstancesClient() - op_client = compute_v1.ZoneOperationsClient() instance_data = instance_client.get( project=project_id, zone=zone, instance=instance_name @@ -55,18 +103,14 @@ def start_instance_with_encryption_key( enc_data = compute_v1.InstancesStartWithEncryptionKeyRequest() enc_data.disks = [disk_data] - op = instance_client.start_with_encryption_key_unary( + operation = instance_client.start_with_encryption_key( project=project_id, zone=zone, instance=instance_name, instances_start_with_encryption_key_request_resource=enc_data, ) - start = time.time() - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() + wait_for_extended_operation(operation, "instance start (with encrypted disk)") return diff --git a/compute/compute/snippets/instances/stop.py b/compute/compute/snippets/instances/stop.py index b8b2f8b4e75..7cbaeb16bca 100644 --- a/compute/compute/snippets/instances/stop.py +++ b/compute/compute/snippets/instances/stop.py @@ -20,11 +20,60 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def stop_instance(project_id: str, zone: str, instance_name: str) -> None: """ Stops a running Google Compute Engine instance. @@ -34,17 +83,11 @@ def stop_instance(project_id: str, zone: str, instance_name: str) -> None: instance_name: name of the instance your want to stop. """ instance_client = compute_v1.InstancesClient() - op_client = compute_v1.ZoneOperationsClient() - op = instance_client.stop_unary( + operation = instance_client.stop( project=project_id, zone=zone, instance=instance_name ) - - start = time.time() - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() + wait_for_extended_operation(operation, "instance stopping") return diff --git a/compute/compute/snippets/instances/suspend.py b/compute/compute/snippets/instances/suspend.py index 4427176a03a..bf4a4c30df9 100644 --- a/compute/compute/snippets/instances/suspend.py +++ b/compute/compute/snippets/instances/suspend.py @@ -20,11 +20,60 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def suspend_instance(project_id: str, zone: str, instance_name: str) -> None: """ Suspend a running Google Compute Engine instance. @@ -34,17 +83,12 @@ def suspend_instance(project_id: str, zone: str, instance_name: str) -> None: instance_name: name of the instance your want to suspend. """ instance_client = compute_v1.InstancesClient() - op_client = compute_v1.ZoneOperationsClient() - op = instance_client.suspend_unary( + operation = instance_client.suspend( project=project_id, zone=zone, instance=instance_name ) - start = time.time() - while op.status != compute_v1.Operation.Status.DONE: - op = op_client.wait(operation=op.name, zone=zone, project=project_id) - if time.time() - start >= 300: # 5 minutes - raise TimeoutError() + wait_for_extended_operation(operation, "instance suspend") return diff --git a/compute/compute/snippets/snapshots/create.py b/compute/compute/snippets/snapshots/create.py index adfdf8e28d3..0e9a472df30 100644 --- a/compute/compute/snippets/snapshots/create.py +++ b/compute/compute/snippets/snapshots/create.py @@ -21,10 +21,58 @@ # [START compute_snapshot_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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def create_snapshot( project_id: str, zone: str, disk_name: str, snapshot_name: str ) -> compute_v1.Snapshot: @@ -47,19 +95,9 @@ def create_snapshot( snapshot.name = snapshot_name snapshot_client = compute_v1.SnapshotsClient() - operation = snapshot_client.insert_unary( - project=project_id, snapshot_resource=snapshot - ) - op_client = compute_v1.GlobalOperationsClient() - operation = op_client.wait(project=project_id, operation=operation.name) - - if operation.error: - print("Error during snapshot creation:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warnings during snapshot creation:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + 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/compute/snippets/snapshots/delete.py b/compute/compute/snippets/snapshots/delete.py index ffa0abab522..ebbf0e04bc4 100644 --- a/compute/compute/snippets/snapshots/delete.py +++ b/compute/compute/snippets/snapshots/delete.py @@ -21,11 +21,58 @@ # [START compute_snapshot_delete] import sys -from typing import NoReturn +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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def delete_snapshot(project_id: str, snapshot_name: str) -> NoReturn: """ Delete a snapshot of a disk. @@ -36,17 +83,9 @@ def delete_snapshot(project_id: str, snapshot_name: str) -> NoReturn: """ snapshot_client = compute_v1.SnapshotsClient() - operation = snapshot_client.delete_unary(project=project_id, snapshot=snapshot_name) - op_client = compute_v1.GlobalOperationsClient() - operation = op_client.wait(project=project_id, operation=operation.name) + operation = snapshot_client.delete(project=project_id, snapshot=snapshot_name) - if operation.error: - print("Error during snapshot deletion:", operation.error, file=sys.stderr) - raise RuntimeError(operation.error) - if operation.warnings: - print("Warnings during snapshot deletion:\n", file=sys.stderr) - for warning in operation.warnings: - print(f" - {warning.code}: {warning.message}", file=sys.stderr) + wait_for_extended_operation(operation, "snapshot deletion") return diff --git a/compute/compute/snippets/tests/test_create_vm.py b/compute/compute/snippets/tests/test_create_vm.py index fbda262ea25..58382ed924e 100644 --- a/compute/compute/snippets/tests/test_create_vm.py +++ b/compute/compute/snippets/tests/test_create_vm.py @@ -230,9 +230,6 @@ def test_create_with_existing_disks(boot_disk, empty_disk): [boot_disk.name, empty_disk.name]) try: - print(instance.disks) - for disk in instance.disks: - print(disk, dir(disk), type(disk), disk.disk_size_gb) assert any( disk.disk_size_gb == 13 for disk in instance.disks ) diff --git a/compute/compute/snippets/usage_report/usage_reports.py b/compute/compute/snippets/usage_report/usage_reports.py index 85e5ea9cd49..1c89bf3e8e0 100644 --- a/compute/compute/snippets/usage_report/usage_reports.py +++ b/compute/compute/snippets/usage_report/usage_reports.py @@ -26,6 +26,10 @@ # [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 @@ -35,6 +39,54 @@ # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def set_usage_export_bucket( project_id: str, bucket_name: str, report_name_prefix: str = "" ) -> None: @@ -64,14 +116,11 @@ def set_usage_export_bucket( ) projects_client = compute_v1.ProjectsClient() - operation = projects_client.set_usage_export_bucket_unary( + operation = projects_client.set_usage_export_bucket( project=project_id, usage_export_location_resource=usage_export_location ) - op_client = compute_v1.GlobalOperationsClient() - - while operation.status != compute_v1.Operation.Status.DONE: - operation = op_client.wait(operation=operation.name, project=project_id) + wait_for_extended_operation(operation, "setting GCE usage bucket") # [END compute_usage_report_set] @@ -115,6 +164,54 @@ def get_usage_export_bucket(project_id: str) -> compute_v1.UsageExportLocation: # [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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + def disable_usage_export(project_id: str) -> None: """ Disable Compute Engine usage export bucket for the Cloud Project. @@ -126,14 +223,11 @@ def disable_usage_export(project_id: str) -> None: # Setting `usage_export_location_resource` to an # empty object will disable the usage report generation. - operation = projects_client.set_usage_export_bucket_unary( + operation = projects_client.set_usage_export_bucket( project=project_id, usage_export_location_resource={} ) - op_client = compute_v1.GlobalOperationsClient() - - while operation.status != compute_v1.Operation.Status.DONE: - operation = op_client.wait(operation=operation.name, project=project_id) + wait_for_extended_operation(operation, "disabling GCE usage bucket") # [END compute_usage_report_disable] From ce2e992ed44526aab2b2c443a3c27a8eb660c5d7 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Thu, 21 Apr 2022 21:17:19 +0200 Subject: [PATCH 080/113] chore(deps): update dependency google-cloud-compute to v1.3.0 (#269) --- compute/compute/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index f8dcc530c19..32ff48a263a 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 black==22.3.0 -google-cloud-compute==1.2.0 +google-cloud-compute==1.3.0 From ce1816eb7aa961e6f4d8607263d46c84344a1ab9 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 25 Apr 2022 17:55:15 +0200 Subject: [PATCH 081/113] chore(deps): update dependency pytest to v7.1.2 (#271) --- compute/compute/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements-test.txt b/compute/compute/requirements-test.txt index d40fd21e53a..db25a383971 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -1,4 +1,4 @@ -pytest==7.1.1 +pytest==7.1.2 pytest-parallel==0.1.1 flaky==3.7.0 google-cloud-storage==2.3.0 From 781e7928762a12373f60a45aee389c31ebf67360 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Tue, 26 Apr 2022 13:24:46 +0200 Subject: [PATCH 082/113] docs(samples): Adding samples for image creation and deletion (#270) * chore(samples): Create image function Co-authored-by: Anthonios Partheniou --- compute/compute/ingredients/images/create.py | 83 ++++++++++ compute/compute/ingredients/images/delete.py | 37 +++++ compute/compute/recipes/images/create.py | 24 +++ compute/compute/recipes/images/delete.py | 22 +++ compute/compute/snippets/images/create.py | 151 ++++++++++++++++++ compute/compute/snippets/images/delete.py | 89 +++++++++++ compute/compute/snippets/tests/test_images.py | 42 +++++ 7 files changed, 448 insertions(+) create mode 100644 compute/compute/ingredients/images/create.py create mode 100644 compute/compute/ingredients/images/delete.py create mode 100644 compute/compute/recipes/images/create.py create mode 100644 compute/compute/recipes/images/delete.py create mode 100644 compute/compute/snippets/images/create.py create mode 100644 compute/compute/snippets/images/delete.py diff --git a/compute/compute/ingredients/images/create.py b/compute/compute/ingredients/images/create.py new file mode 100644 index 00000000000..805f1e9c7b4 --- /dev/null +++ b/compute/compute/ingredients/images/create.py @@ -0,0 +1,83 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 +import warnings + +# +STOPPED_MACHINE_STATUS = ( + compute_v1.Instance.Status.TERMINATED.name, + compute_v1.Instance.Status.STOPPED.name +) + + +def create_image(project_id: str, zone: str, source_disk_name: str, image_name: str, + storage_location: 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. + """ + 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") + + return image_client.get(project=project_id, image=image_name) +# diff --git a/compute/compute/ingredients/images/delete.py b/compute/compute/ingredients/images/delete.py new file mode 100644 index 00000000000..18059fd5348 --- /dev/null +++ b/compute/compute/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/compute/recipes/images/create.py b/compute/compute/recipes/images/create.py new file mode 100644 index 00000000000..22c0d19cc50 --- /dev/null +++ b/compute/compute/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/compute/recipes/images/delete.py b/compute/compute/recipes/images/delete.py new file mode 100644 index 00000000000..6796dae9eea --- /dev/null +++ b/compute/compute/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/compute/snippets/images/create.py b/compute/compute/snippets/images/create.py new file mode 100644 index 00000000000..af2a96bd811 --- /dev/null +++ b/compute/compute/snippets/images/create.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_windows_image_create] +# [START compute_images_create] +import sys +import time +from typing import Any +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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + return result + + +STOPPED_MACHINE_STATUS = ( + compute_v1.Instance.Status.TERMINATED.name, + compute_v1.Instance.Status.STOPPED.name, +) + + +def create_image( + project_id: str, + zone: str, + source_disk_name: str, + image_name: str, + storage_location: 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. + """ + 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") + + return image_client.get(project=project_id, image=image_name) + + +# [END compute_images_create] +# [END compute_windows_image_create] diff --git a/compute/compute/snippets/images/delete.py b/compute/compute/snippets/images/delete.py new file mode 100644 index 00000000000..7216674a2e1 --- /dev/null +++ b/compute/compute/snippets/images/delete.py @@ -0,0 +1,89 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + 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/compute/snippets/tests/test_images.py b/compute/compute/snippets/tests/test_images.py index 18852ac09a0..394114b2972 100644 --- a/compute/compute/snippets/tests/test_images.py +++ b/compute/compute/snippets/tests/test_images.py @@ -11,10 +11,37 @@ # WITHOUT 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 ..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 +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) + def test_list_images(): images = list_images("debian-cloud") @@ -32,3 +59,18 @@ def test_get_image(): image2 = get_image("debian-cloud", image.name) assert image == image2 + + +def test_create_delete_image(test_disk): + test_image_name = "test-image-" + uuid.uuid4().hex[:10] + new_image = create_image(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.") From d3b238b441919b6ed03b6db575116d238e2875e7 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Mon, 2 May 2022 17:12:12 +0200 Subject: [PATCH 083/113] docs(samples): Adding samples for image related operations (#277) Samples for the [Create, delete, and deprecate custom images](https://cloud.google.com/compute/docs/images/create-delete-deprecate-private-images) page. --- compute/compute/ingredients/images/create.py | 14 +- .../ingredients/images/create_from_image.py | 69 +++++++++ .../images/create_from_snapshot.py | 71 ++++++++++ .../images/set_depracation_status.py | 46 ++++++ .../recipes/images/create_from_image.py | 22 +++ .../recipes/images/create_from_snapshot.py | 22 +++ .../recipes/images/set_deprecation_status.py | 23 +++ compute/compute/snippets/images/create.py | 12 +- .../snippets/images/create_from_image.py | 127 +++++++++++++++++ .../snippets/images/create_from_snapshot.py | 131 ++++++++++++++++++ .../snippets/images/set_deprecation_status.py | 103 ++++++++++++++ compute/compute/snippets/tests/test_images.py | 71 +++++++++- 12 files changed, 699 insertions(+), 12 deletions(-) create mode 100644 compute/compute/ingredients/images/create_from_image.py create mode 100644 compute/compute/ingredients/images/create_from_snapshot.py create mode 100644 compute/compute/ingredients/images/set_depracation_status.py create mode 100644 compute/compute/recipes/images/create_from_image.py create mode 100644 compute/compute/recipes/images/create_from_snapshot.py create mode 100644 compute/compute/recipes/images/set_deprecation_status.py create mode 100644 compute/compute/snippets/images/create_from_image.py create mode 100644 compute/compute/snippets/images/create_from_snapshot.py create mode 100644 compute/compute/snippets/images/set_deprecation_status.py diff --git a/compute/compute/ingredients/images/create.py b/compute/compute/ingredients/images/create.py index 805f1e9c7b4..b029d7aaa3c 100644 --- a/compute/compute/ingredients/images/create.py +++ b/compute/compute/ingredients/images/create.py @@ -17,10 +17,11 @@ # 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 import warnings +from typing import Optional + +from google.cloud import compute_v1 # STOPPED_MACHINE_STATUS = ( @@ -29,8 +30,8 @@ ) -def create_image(project_id: str, zone: str, source_disk_name: str, image_name: str, - storage_location: str = None, force_create: bool = False) -> compute_v1.Image: +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. @@ -44,6 +45,9 @@ def create_image(project_id: str, zone: str, source_disk_name: str, image_name: 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() @@ -77,7 +81,7 @@ def create_image(project_id: str, zone: str, source_disk_name: str, image_name: operation = image_client.insert(project=project_id, image_resource=image) - wait_for_extended_operation(operation, "image creation") + wait_for_extended_operation(operation, "image creation from disk") return image_client.get(project=project_id, image=image_name) # diff --git a/compute/compute/ingredients/images/create_from_image.py b/compute/compute/ingredients/images/create_from_image.py new file mode 100644 index 00000000000..07b6d1c8e76 --- /dev/null +++ b/compute/compute/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/compute/ingredients/images/create_from_snapshot.py b/compute/compute/ingredients/images/create_from_snapshot.py new file mode 100644 index 00000000000..f882e24914b --- /dev/null +++ b/compute/compute/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/compute/ingredients/images/set_depracation_status.py b/compute/compute/ingredients/images/set_depracation_status.py new file mode 100644 index 00000000000..a817a03e63e --- /dev/null +++ b/compute/compute/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/compute/recipes/images/create_from_image.py b/compute/compute/recipes/images/create_from_image.py new file mode 100644 index 00000000000..d1e56b4419e --- /dev/null +++ b/compute/compute/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/compute/recipes/images/create_from_snapshot.py b/compute/compute/recipes/images/create_from_snapshot.py new file mode 100644 index 00000000000..a64f33fe31b --- /dev/null +++ b/compute/compute/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/compute/recipes/images/set_deprecation_status.py b/compute/compute/recipes/images/set_deprecation_status.py new file mode 100644 index 00000000000..e92d5254211 --- /dev/null +++ b/compute/compute/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/compute/snippets/images/create.py b/compute/compute/snippets/images/create.py index af2a96bd811..b2227f80825 100644 --- a/compute/compute/snippets/images/create.py +++ b/compute/compute/snippets/images/create.py @@ -22,8 +22,7 @@ # [START compute_windows_image_create] # [START compute_images_create] import sys -import time -from typing import Any +from typing import Any, Optional import warnings from google.api_core.extended_operation import ExtendedOperation @@ -82,12 +81,12 @@ def wait_for_extended_operation( ) -def create_image( +def create_image_from_disk( project_id: str, zone: str, source_disk_name: str, image_name: str, - storage_location: str = None, + storage_location: Optional[str] = None, force_create: bool = False, ) -> compute_v1.Image: """ @@ -103,6 +102,9 @@ def create_image( 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() @@ -142,7 +144,7 @@ def create_image( operation = image_client.insert(project=project_id, image_resource=image) - wait_for_extended_operation(operation, "image creation") + wait_for_extended_operation(operation, "image creation from disk") return image_client.get(project=project_id, image=image_name) diff --git a/compute/compute/snippets/images/create_from_image.py b/compute/compute/snippets/images/create_from_image.py new file mode 100644 index 00000000000..7499998cdb0 --- /dev/null +++ b/compute/compute/snippets/images/create_from_image.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_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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + 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/compute/snippets/images/create_from_snapshot.py b/compute/compute/snippets/images/create_from_snapshot.py new file mode 100644 index 00000000000..c0e7db7ade8 --- /dev/null +++ b/compute/compute/snippets/images/create_from_snapshot.py @@ -0,0 +1,131 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + 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/compute/snippets/images/set_deprecation_status.py b/compute/compute/snippets/images/set_deprecation_status.py new file mode 100644 index 00000000000..2f57dfc51ca --- /dev/null +++ b/compute/compute/snippets/images/set_deprecation_status.py @@ -0,0 +1,103 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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, + ) + print(f"Operation ID: {operation.name}") + raise operation.exception() or RuntimeError(operation.error_message) + + if operation.warnings: + print(f"Warnings during {verbose_name}:\n", file=sys.stderr) + for warning in operation.warnings: + print(f" - {warning.code}: {warning.message}", file=sys.stderr) + + 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/compute/snippets/tests/test_images.py b/compute/compute/snippets/tests/test_images.py index 394114b2972..ae5db9dc87f 100644 --- a/compute/compute/snippets/tests/test_images.py +++ b/compute/compute/snippets/tests/test_images.py @@ -19,11 +19,16 @@ from ..disks.create_from_image import create_disk_from_image from ..disks.delete import delete_disk -from ..images.create import create_image +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' @@ -43,6 +48,39 @@ def test_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.zone.rsplit('/')[-1], test_disk.name, test_snap_name) + 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: @@ -63,7 +101,7 @@ def test_get_image(): def test_create_delete_image(test_disk): test_image_name = "test-image-" + uuid.uuid4().hex[:10] - new_image = create_image(PROJECT, ZONE, test_disk.name, test_image_name) + 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 @@ -74,3 +112,32 @@ def test_create_delete_image(test_disk): 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('debian-cloud', 'debian-11') + new_image = create_image_from_image(PROJECT, src_img.name, autodelete_image_name, 'debian-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 From e4077aeeb2fcab811ea9ad412aec75b40cd99632 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 4 May 2022 18:22:13 +0200 Subject: [PATCH 084/113] docs(samples): Improving snapshot samples (#276) Improving the code snippets for disk snapshots, so they can be used on this page: [Create and manage disk snapshots ](https://cloud.google.com/compute/docs/disks/create-snapshots) --- .../operations/handle_extended_operation.py | 9 +- .../compute/ingredients/snapshots/create.py | 42 +++++- compute/compute/ingredients/snapshots/get.py | 42 ++++++ compute/compute/ingredients/snapshots/list.py | 6 +- .../recipes/snapshots/delete_by_filter.py | 37 +++++ compute/compute/recipes/snapshots/get.py | 21 +++ .../snippets/disks/autodelete_change.py | 7 +- .../snippets/disks/create_empty_disk.py | 7 +- .../snippets/disks/create_from_image.py | 7 +- .../snippets/disks/create_from_snapshot.py | 7 +- compute/compute/snippets/disks/delete.py | 7 +- compute/compute/snippets/firewall/create.py | 7 +- compute/compute/snippets/firewall/delete.py | 7 +- compute/compute/snippets/firewall/patch.py | 7 +- compute/compute/snippets/images/create.py | 7 +- .../snippets/images/create_from_image.py | 7 +- .../snippets/images/create_from_snapshot.py | 7 +- compute/compute/snippets/images/delete.py | 7 +- .../snippets/images/set_deprecation_status.py | 7 +- .../snippets/instance_templates/create.py | 7 +- .../create_from_instance.py | 7 +- .../instance_templates/create_with_subnet.py | 7 +- .../snippets/instance_templates/delete.py | 7 +- compute/compute/snippets/instances/create.py | 7 +- .../create_from_custom_image.py | 7 +- .../create_from_public_image.py | 7 +- .../create_from_snapshot.py | 7 +- .../create_with_additional_disk.py | 7 +- .../create_with_existing_disks.py | 7 +- .../create_with_snapshotted_data_disk.py | 7 +- .../snippets/instances/create_with_subnet.py | 7 +- .../instances/custom_hostname/create.py | 7 +- .../create_shared_with_helper.py | 7 +- .../create_with_helper.py | 7 +- .../create_without_helper.py | 7 +- .../extra_mem_no_helper.py | 7 +- .../custom_machine_types/update_memory.py | 7 +- compute/compute/snippets/instances/delete.py | 7 +- .../instances/delete_protection/create.py | 7 +- .../instances/delete_protection/set.py | 7 +- .../create_from_template.py | 7 +- .../create_from_template_with_overrides.py | 7 +- .../preemptible/create_preemptible.py | 7 +- compute/compute/snippets/instances/reset.py | 7 +- compute/compute/snippets/instances/resume.py | 7 +- compute/compute/snippets/instances/start.py | 7 +- .../snippets/instances/start_encrypted.py | 7 +- compute/compute/snippets/instances/stop.py | 7 +- compute/compute/snippets/instances/suspend.py | 7 +- compute/compute/snippets/snapshots/create.py | 58 ++++++-- compute/compute/snippets/snapshots/delete.py | 7 +- .../snippets/snapshots/delete_by_filter.py | 128 ++++++++++++++++++ compute/compute/snippets/snapshots/get.py | 45 ++++++ compute/compute/snippets/snapshots/list.py | 6 +- compute/compute/snippets/tests/test_images.py | 6 +- .../compute/snippets/tests/test_snapshots.py | 8 +- .../snippets/usage_report/usage_reports.py | 14 +- 57 files changed, 563 insertions(+), 167 deletions(-) create mode 100644 compute/compute/ingredients/snapshots/get.py create mode 100644 compute/compute/recipes/snapshots/delete_by_filter.py create mode 100644 compute/compute/recipes/snapshots/get.py create mode 100644 compute/compute/snippets/snapshots/delete_by_filter.py create mode 100644 compute/compute/snippets/snapshots/get.py diff --git a/compute/compute/ingredients/operations/handle_extended_operation.py b/compute/compute/ingredients/operations/handle_extended_operation.py index 7c75c17f7aa..1a0c74213eb 100644 --- a/compute/compute/ingredients/operations/handle_extended_operation.py +++ b/compute/compute/ingredients/operations/handle_extended_operation.py @@ -55,14 +55,15 @@ def wait_for_extended_operation( 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) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result # diff --git a/compute/compute/ingredients/snapshots/create.py b/compute/compute/ingredients/snapshots/create.py index 1c876a206bb..9b0e0cac78b 100644 --- a/compute/compute/ingredients/snapshots/create.py +++ b/compute/compute/ingredients/snapshots/create.py @@ -16,30 +16,60 @@ # 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, zone: str, disk_name: str, snapshot_name: str) -> compute_v1.Snapshot: +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. - zone: name of the zone in which is the disk you want to snapshot. + 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. """ - disk_client = compute_v1.DisksClient() - disk = disk_client.get(project=project_id, zone=zone, disk=disk_name) + 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) diff --git a/compute/compute/ingredients/snapshots/get.py b/compute/compute/ingredients/snapshots/get.py new file mode 100644 index 00000000000..d645880feed --- /dev/null +++ b/compute/compute/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/compute/ingredients/snapshots/list.py b/compute/compute/ingredients/snapshots/list.py index 87539f1738d..a87b2260169 100644 --- a/compute/compute/ingredients/snapshots/list.py +++ b/compute/compute/ingredients/snapshots/list.py @@ -22,13 +22,13 @@ # -def list_snapshots(project_id: str, filter_: str = "") -> Iterable[compute_v1.Snapshot]: +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: + 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: @@ -38,7 +38,7 @@ def list_snapshots(project_id: str, filter_: str = "") -> Iterable[compute_v1.Sn snapshot_client = compute_v1.SnapshotsClient() request = compute_v1.ListSnapshotsRequest() request.project = project_id - request.filter = filter_ + request.filter = filter return snapshot_client.list(request) # diff --git a/compute/compute/recipes/snapshots/delete_by_filter.py b/compute/compute/recipes/snapshots/delete_by_filter.py new file mode 100644 index 00000000000..d250c37a9d5 --- /dev/null +++ b/compute/compute/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/compute/recipes/snapshots/get.py b/compute/compute/recipes/snapshots/get.py new file mode 100644 index 00000000000..bb0a324aeb9 --- /dev/null +++ b/compute/compute/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/compute/snippets/disks/autodelete_change.py b/compute/compute/snippets/disks/autodelete_change.py index 197e275d67f..bb903a7268b 100644 --- a/compute/compute/snippets/disks/autodelete_change.py +++ b/compute/compute/snippets/disks/autodelete_change.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/disks/create_empty_disk.py b/compute/compute/snippets/disks/create_empty_disk.py index ff86b77d02c..331f0b21283 100644 --- a/compute/compute/snippets/disks/create_empty_disk.py +++ b/compute/compute/snippets/disks/create_empty_disk.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/disks/create_from_image.py b/compute/compute/snippets/disks/create_from_image.py index 51ccb8e8cc5..0b8e17ea060 100644 --- a/compute/compute/snippets/disks/create_from_image.py +++ b/compute/compute/snippets/disks/create_from_image.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/disks/create_from_snapshot.py b/compute/compute/snippets/disks/create_from_snapshot.py index 8aef45adeff..deb0f9dfd5f 100644 --- a/compute/compute/snippets/disks/create_from_snapshot.py +++ b/compute/compute/snippets/disks/create_from_snapshot.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/disks/delete.py b/compute/compute/snippets/disks/delete.py index f5c21d6fe80..c396911eb21 100644 --- a/compute/compute/snippets/disks/delete.py +++ b/compute/compute/snippets/disks/delete.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/firewall/create.py b/compute/compute/snippets/firewall/create.py index 8d80a08d8b2..bcdacb55920 100644 --- a/compute/compute/snippets/firewall/create.py +++ b/compute/compute/snippets/firewall/create.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/firewall/delete.py b/compute/compute/snippets/firewall/delete.py index fe2c7c08ea5..f645f618d17 100644 --- a/compute/compute/snippets/firewall/delete.py +++ b/compute/compute/snippets/firewall/delete.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/firewall/patch.py b/compute/compute/snippets/firewall/patch.py index e81624dc908..f288c35ade0 100644 --- a/compute/compute/snippets/firewall/patch.py +++ b/compute/compute/snippets/firewall/patch.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/images/create.py b/compute/compute/snippets/images/create.py index b2227f80825..21fa76dd3fa 100644 --- a/compute/compute/snippets/images/create.py +++ b/compute/compute/snippets/images/create.py @@ -63,14 +63,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/images/create_from_image.py b/compute/compute/snippets/images/create_from_image.py index 7499998cdb0..3dddea1a599 100644 --- a/compute/compute/snippets/images/create_from_image.py +++ b/compute/compute/snippets/images/create_from_image.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/images/create_from_snapshot.py b/compute/compute/snippets/images/create_from_snapshot.py index c0e7db7ade8..18365ea0632 100644 --- a/compute/compute/snippets/images/create_from_snapshot.py +++ b/compute/compute/snippets/images/create_from_snapshot.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/images/delete.py b/compute/compute/snippets/images/delete.py index 7216674a2e1..5412bcd686a 100644 --- a/compute/compute/snippets/images/delete.py +++ b/compute/compute/snippets/images/delete.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/images/set_deprecation_status.py b/compute/compute/snippets/images/set_deprecation_status.py index 2f57dfc51ca..c98ee04adc3 100644 --- a/compute/compute/snippets/images/set_deprecation_status.py +++ b/compute/compute/snippets/images/set_deprecation_status.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instance_templates/create.py b/compute/compute/snippets/instance_templates/create.py index 4b63601c89c..1041206c914 100644 --- a/compute/compute/snippets/instance_templates/create.py +++ b/compute/compute/snippets/instance_templates/create.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instance_templates/create_from_instance.py b/compute/compute/snippets/instance_templates/create_from_instance.py index 7ceff5074e6..a77c813eefb 100644 --- a/compute/compute/snippets/instance_templates/create_from_instance.py +++ b/compute/compute/snippets/instance_templates/create_from_instance.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instance_templates/create_with_subnet.py b/compute/compute/snippets/instance_templates/create_with_subnet.py index 922a0ec27a9..3051e949534 100644 --- a/compute/compute/snippets/instance_templates/create_with_subnet.py +++ b/compute/compute/snippets/instance_templates/create_with_subnet.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instance_templates/delete.py b/compute/compute/snippets/instance_templates/delete.py index d024f70c978..382fd64d4c8 100644 --- a/compute/compute/snippets/instance_templates/delete.py +++ b/compute/compute/snippets/instance_templates/delete.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/create.py b/compute/compute/snippets/instances/create.py index 512ed6176d9..e538b5b59a2 100644 --- a/compute/compute/snippets/instances/create.py +++ b/compute/compute/snippets/instances/create.py @@ -117,14 +117,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py b/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py index 16dfa1b86d4..4ddc4138f3d 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py @@ -117,14 +117,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py b/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py index 881aa2c85f3..439d985410e 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py @@ -117,14 +117,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py b/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py index f7397b0adc4..a3dde1bf898 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py @@ -99,14 +99,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py b/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py index b506a9f0912..e923f73624a 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py @@ -147,14 +147,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py b/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py index 121582d9126..22f92da6073 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py @@ -75,14 +75,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py b/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py index dbffbb174ee..c86108b406f 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py @@ -154,14 +154,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/create_with_subnet.py b/compute/compute/snippets/instances/create_with_subnet.py index 3c28b84707e..4ab870837ea 100644 --- a/compute/compute/snippets/instances/create_with_subnet.py +++ b/compute/compute/snippets/instances/create_with_subnet.py @@ -117,14 +117,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/custom_hostname/create.py b/compute/compute/snippets/instances/custom_hostname/create.py index 377a3169f51..03498dec9ac 100644 --- a/compute/compute/snippets/instances/custom_hostname/create.py +++ b/compute/compute/snippets/instances/custom_hostname/create.py @@ -117,14 +117,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py index 4f37ae2a256..9d41d09819c 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py @@ -311,14 +311,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py index 499c6305955..cdb85464ebe 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py @@ -311,14 +311,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py index d7e13d8c16d..42da51444cc 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py @@ -117,14 +117,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py b/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py index 05975032833..de1c0057362 100644 --- a/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py @@ -117,14 +117,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/custom_machine_types/update_memory.py b/compute/compute/snippets/instances/custom_machine_types/update_memory.py index b61a2a2b570..d6e94237466 100644 --- a/compute/compute/snippets/instances/custom_machine_types/update_memory.py +++ b/compute/compute/snippets/instances/custom_machine_types/update_memory.py @@ -62,14 +62,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/delete.py b/compute/compute/snippets/instances/delete.py index af847455836..1a8eb3edfb9 100644 --- a/compute/compute/snippets/instances/delete.py +++ b/compute/compute/snippets/instances/delete.py @@ -62,14 +62,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/delete_protection/create.py b/compute/compute/snippets/instances/delete_protection/create.py index fe9d26e60e3..addf7a9c775 100644 --- a/compute/compute/snippets/instances/delete_protection/create.py +++ b/compute/compute/snippets/instances/delete_protection/create.py @@ -117,14 +117,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/delete_protection/set.py b/compute/compute/snippets/instances/delete_protection/set.py index 3a60083066e..f8c692a3155 100644 --- a/compute/compute/snippets/instances/delete_protection/set.py +++ b/compute/compute/snippets/instances/delete_protection/set.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/from_instance_template/create_from_template.py b/compute/compute/snippets/instances/from_instance_template/create_from_template.py index 3abd6cc469e..bcc4b77e970 100644 --- a/compute/compute/snippets/instances/from_instance_template/create_from_template.py +++ b/compute/compute/snippets/instances/from_instance_template/create_from_template.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py b/compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py index eca6f0a49ca..da62d3ecf87 100644 --- a/compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py +++ b/compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/preemptible/create_preemptible.py b/compute/compute/snippets/instances/preemptible/create_preemptible.py index 3e2ef4b897a..a85f0135501 100644 --- a/compute/compute/snippets/instances/preemptible/create_preemptible.py +++ b/compute/compute/snippets/instances/preemptible/create_preemptible.py @@ -117,14 +117,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/reset.py b/compute/compute/snippets/instances/reset.py index 1764ab632c1..110d84eb1d1 100644 --- a/compute/compute/snippets/instances/reset.py +++ b/compute/compute/snippets/instances/reset.py @@ -62,14 +62,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/resume.py b/compute/compute/snippets/instances/resume.py index 2c6cf68cbf2..715a1681b72 100644 --- a/compute/compute/snippets/instances/resume.py +++ b/compute/compute/snippets/instances/resume.py @@ -62,14 +62,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/start.py b/compute/compute/snippets/instances/start.py index 9dbf039aee2..70e3e34481d 100644 --- a/compute/compute/snippets/instances/start.py +++ b/compute/compute/snippets/instances/start.py @@ -62,14 +62,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/start_encrypted.py b/compute/compute/snippets/instances/start_encrypted.py index ef08fb1dd63..ecfa1cd2620 100644 --- a/compute/compute/snippets/instances/start_encrypted.py +++ b/compute/compute/snippets/instances/start_encrypted.py @@ -62,14 +62,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/stop.py b/compute/compute/snippets/instances/stop.py index 7cbaeb16bca..1b675c4f115 100644 --- a/compute/compute/snippets/instances/stop.py +++ b/compute/compute/snippets/instances/stop.py @@ -62,14 +62,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/instances/suspend.py b/compute/compute/snippets/instances/suspend.py index bf4a4c30df9..7dd286a3c4b 100644 --- a/compute/compute/snippets/instances/suspend.py +++ b/compute/compute/snippets/instances/suspend.py @@ -62,14 +62,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/snapshots/create.py b/compute/compute/snippets/snapshots/create.py index 0e9a472df30..fb1b75c0fa5 100644 --- a/compute/compute/snippets/snapshots/create.py +++ b/compute/compute/snippets/snapshots/create.py @@ -21,7 +21,7 @@ # [START compute_snapshot_create] import sys -from typing import Any +from typing import Any, Optional from google.api_core.extended_operation import ExtendedOperation from google.cloud import compute_v1 @@ -61,38 +61,78 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result def create_snapshot( - project_id: str, zone: str, disk_name: str, snapshot_name: str + 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. - zone: name of the zone in which is the disk you want to snapshot. + 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. """ - disk_client = compute_v1.DisksClient() - disk = disk_client.get(project=project_id, zone=zone, disk=disk_name) + 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) diff --git a/compute/compute/snippets/snapshots/delete.py b/compute/compute/snippets/snapshots/delete.py index ebbf0e04bc4..5735dfba25e 100644 --- a/compute/compute/snippets/snapshots/delete.py +++ b/compute/compute/snippets/snapshots/delete.py @@ -61,14 +61,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result diff --git a/compute/compute/snippets/snapshots/delete_by_filter.py b/compute/compute/snippets/snapshots/delete_by_filter.py new file mode 100644 index 00000000000..a71bda1de45 --- /dev/null +++ b/compute/compute/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/compute/snippets/snapshots/get.py b/compute/compute/snippets/snapshots/get.py new file mode 100644 index 00000000000..bb6fa1d38a0 --- /dev/null +++ b/compute/compute/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/compute/snippets/snapshots/list.py b/compute/compute/snippets/snapshots/list.py index f7db91dbbdc..4c5a89761e9 100644 --- a/compute/compute/snippets/snapshots/list.py +++ b/compute/compute/snippets/snapshots/list.py @@ -25,13 +25,13 @@ from google.cloud import compute_v1 -def list_snapshots(project_id: str, filter_: str = "") -> Iterable[compute_v1.Snapshot]: +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: + 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: @@ -41,7 +41,7 @@ def list_snapshots(project_id: str, filter_: str = "") -> Iterable[compute_v1.Sn snapshot_client = compute_v1.SnapshotsClient() request = compute_v1.ListSnapshotsRequest() request.project = project_id - request.filter = filter_ + request.filter = filter return snapshot_client.list(request) diff --git a/compute/compute/snippets/tests/test_images.py b/compute/compute/snippets/tests/test_images.py index ae5db9dc87f..b6f61b3c821 100644 --- a/compute/compute/snippets/tests/test_images.py +++ b/compute/compute/snippets/tests/test_images.py @@ -54,7 +54,7 @@ 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.zone.rsplit('/')[-1], test_disk.name, test_snap_name) + snap = create_snapshot(PROJECT, test_disk.name, test_snap_name, zone=test_disk.zone.rsplit('/')[-1]) yield snap delete_snapshot(PROJECT, snap.name) @@ -115,8 +115,8 @@ def test_create_delete_image(test_disk): def test_image_from_image(autodelete_image_name): - 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', + 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') diff --git a/compute/compute/snippets/tests/test_snapshots.py b/compute/compute/snippets/tests/test_snapshots.py index 47c2f5f1db1..7e52dcb227a 100644 --- a/compute/compute/snippets/tests/test_snapshots.py +++ b/compute/compute/snippets/tests/test_snapshots.py @@ -21,6 +21,7 @@ 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] @@ -43,7 +44,7 @@ def test_disk(): def test_snapshot_create_delete(test_disk): snapshot_name = "test-snapshot-" + uuid.uuid4().hex[:10] - snapshot = create_snapshot(PROJECT, ZONE, test_disk.name, snapshot_name) + 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): @@ -52,6 +53,11 @@ def test_snapshot_create_delete(test_disk): 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: diff --git a/compute/compute/snippets/usage_report/usage_reports.py b/compute/compute/snippets/usage_report/usage_reports.py index 1c89bf3e8e0..e04a3a8a162 100644 --- a/compute/compute/snippets/usage_report/usage_reports.py +++ b/compute/compute/snippets/usage_report/usage_reports.py @@ -75,14 +75,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result @@ -200,14 +201,15 @@ def wait_for_extended_operation( print( f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}", file=sys.stderr, + flush=True, ) - print(f"Operation ID: {operation.name}") + 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) + 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) + print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True) return result From 322055e44d3bd5597a1218a79e9223c80ab94b3e Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Thu, 5 May 2022 16:10:00 +0200 Subject: [PATCH 085/113] docs(samples): Adding Windows server samples (#274) * docs(samples): Adding Windows instance related sampels Co-authored-by: Anthonios Partheniou --- .../ingredients/firewall/windows_kms.py | 62 ++++ .../create_windows_instance.py | 75 +++++ .../ingredients/instances/get_serial_port.py | 38 +++ compute/compute/ingredients/routes/create.py | 84 +++++ compute/compute/ingredients/routes/delete.py | 40 +++ compute/compute/ingredients/routes/list.py | 38 +++ .../compute/recipes/firewall/windows_kms.py | 23 ++ .../create_windows_instance.py | 31 ++ compute/compute/recipes/routes/create.py | 23 ++ .../recipes/routes/create_kms_route.py | 57 ++++ compute/compute/recipes/routes/delete.py | 23 ++ compute/compute/recipes/routes/list.py | 21 ++ .../compute/snippets/firewall/windows_kms.py | 118 +++++++ .../create_windows_instance.py | 313 ++++++++++++++++++ compute/compute/snippets/routes/create.py | 152 +++++++++ .../snippets/routes/create_kms_route.py | 194 +++++++++++ compute/compute/snippets/routes/delete.py | 94 ++++++ compute/compute/snippets/routes/list.py | 45 +++ compute/compute/snippets/tests/test_route.py | 38 +++ 19 files changed, 1469 insertions(+) create mode 100644 compute/compute/ingredients/firewall/windows_kms.py create mode 100644 compute/compute/ingredients/instances/create_start_instance/create_windows_instance.py create mode 100644 compute/compute/ingredients/instances/get_serial_port.py create mode 100644 compute/compute/ingredients/routes/create.py create mode 100644 compute/compute/ingredients/routes/delete.py create mode 100644 compute/compute/ingredients/routes/list.py create mode 100644 compute/compute/recipes/firewall/windows_kms.py create mode 100644 compute/compute/recipes/instances/create_start_instance/create_windows_instance.py create mode 100644 compute/compute/recipes/routes/create.py create mode 100644 compute/compute/recipes/routes/create_kms_route.py create mode 100644 compute/compute/recipes/routes/delete.py create mode 100644 compute/compute/recipes/routes/list.py create mode 100644 compute/compute/snippets/firewall/windows_kms.py create mode 100644 compute/compute/snippets/instances/create_start_instance/create_windows_instance.py create mode 100644 compute/compute/snippets/routes/create.py create mode 100644 compute/compute/snippets/routes/create_kms_route.py create mode 100644 compute/compute/snippets/routes/delete.py create mode 100644 compute/compute/snippets/routes/list.py create mode 100644 compute/compute/snippets/tests/test_route.py diff --git a/compute/compute/ingredients/firewall/windows_kms.py b/compute/compute/ingredients/firewall/windows_kms.py new file mode 100644 index 00000000000..265d86e666f --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/create_start_instance/create_windows_instance.py b/compute/compute/ingredients/instances/create_start_instance/create_windows_instance.py new file mode 100644 index 00000000000..51d139a5fa4 --- /dev/null +++ b/compute/compute/ingredients/instances/create_start_instance/create_windows_instance.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 +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, + ) + return instance +# diff --git a/compute/compute/ingredients/instances/get_serial_port.py b/compute/compute/ingredients/instances/get_serial_port.py new file mode 100644 index 00000000000..a673e1d27e1 --- /dev/null +++ b/compute/compute/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/compute/ingredients/routes/create.py b/compute/compute/ingredients/routes/create.py new file mode 100644 index 00000000000..a309150c38c --- /dev/null +++ b/compute/compute/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/compute/ingredients/routes/delete.py b/compute/compute/ingredients/routes/delete.py new file mode 100644 index 00000000000..52328a55906 --- /dev/null +++ b/compute/compute/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/compute/ingredients/routes/list.py b/compute/compute/ingredients/routes/list.py new file mode 100644 index 00000000000..494f5da6868 --- /dev/null +++ b/compute/compute/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/compute/recipes/firewall/windows_kms.py b/compute/compute/recipes/firewall/windows_kms.py new file mode 100644 index 00000000000..f0948a35e0c --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/create_start_instance/create_windows_instance.py b/compute/compute/recipes/instances/create_start_instance/create_windows_instance.py new file mode 100644 index 00000000000..d11fd6fcc18 --- /dev/null +++ b/compute/compute/recipes/instances/create_start_instance/create_windows_instance.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/compute/recipes/routes/create.py b/compute/compute/recipes/routes/create.py new file mode 100644 index 00000000000..6862af01252 --- /dev/null +++ b/compute/compute/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/compute/recipes/routes/create_kms_route.py b/compute/compute/recipes/routes/create_kms_route.py new file mode 100644 index 00000000000..ec693259e97 --- /dev/null +++ b/compute/compute/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/compute/recipes/routes/delete.py b/compute/compute/recipes/routes/delete.py new file mode 100644 index 00000000000..5ea48302b4b --- /dev/null +++ b/compute/compute/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/compute/recipes/routes/list.py b/compute/compute/recipes/routes/list.py new file mode 100644 index 00000000000..3869f571b9c --- /dev/null +++ b/compute/compute/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/compute/snippets/firewall/windows_kms.py b/compute/compute/snippets/firewall/windows_kms.py new file mode 100644 index 00000000000..ed869386fa2 --- /dev/null +++ b/compute/compute/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/compute/snippets/instances/create_start_instance/create_windows_instance.py b/compute/compute/snippets/instances/create_start_instance/create_windows_instance.py new file mode 100644 index 00000000000..7ed909d37a6 --- /dev/null +++ b/compute/compute/snippets/instances/create_start_instance/create_windows_instance.py @@ -0,0 +1,313 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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_internal_ip] +import re +import sys +from typing import Any, List, Optional + +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, + 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. + 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.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 + + instance.network_interfaces = [network_interface] + + if preemptible: + # Set the preemptible setting + instance.scheduling = compute_v1.Scheduling() + instance.scheduling.preemptible = True + + 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, + ) + return instance + + +# [END compute_create_windows_instance_internal_ip] diff --git a/compute/compute/snippets/routes/create.py b/compute/compute/snippets/routes/create.py new file mode 100644 index 00000000000..abc3c77e5b3 --- /dev/null +++ b/compute/compute/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/compute/snippets/routes/create_kms_route.py b/compute/compute/snippets/routes/create_kms_route.py new file mode 100644 index 00000000000..eeb2cffc876 --- /dev/null +++ b/compute/compute/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/compute/snippets/routes/delete.py b/compute/compute/snippets/routes/delete.py new file mode 100644 index 00000000000..6ef1de95b1c --- /dev/null +++ b/compute/compute/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/compute/snippets/routes/list.py b/compute/compute/snippets/routes/list.py new file mode 100644 index 00000000000..b4f83fb07d3 --- /dev/null +++ b/compute/compute/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/compute/snippets/tests/test_route.py b/compute/compute/snippets/tests/test_route.py new file mode 100644 index 00000000000..65ccbf81ece --- /dev/null +++ b/compute/compute/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}.") From 2a7f7b0af802bd00b36d22701adc1f6467774638 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Thu, 19 May 2022 16:51:07 +0200 Subject: [PATCH 086/113] chore(deps): update dependency google-cloud-compute to v1.3.1 (#284) --- compute/compute/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index 32ff48a263a..340112169b5 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 black==22.3.0 -google-cloud-compute==1.3.0 +google-cloud-compute==1.3.1 From 52e192f93ed9233247dce03bcbc69e3bbc99235c Mon Sep 17 00:00:00 2001 From: Savija Vijayaraghavan Date: Mon, 27 Jun 2022 16:25:04 +0200 Subject: [PATCH 087/113] chore(docs): Fix a minor typo (#297) * Fix a minor typo * chore(docs): Fix a minor typo --- compute/compute/ingredients/instances/resume.py | 2 +- compute/compute/ingredients/instances/suspend.py | 4 ++-- compute/compute/snippets/instances/resume.py | 2 +- compute/compute/snippets/instances/suspend.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compute/compute/ingredients/instances/resume.py b/compute/compute/ingredients/instances/resume.py index 2dcfcd325e3..f29ffab6aee 100644 --- a/compute/compute/ingredients/instances/resume.py +++ b/compute/compute/ingredients/instances/resume.py @@ -28,7 +28,7 @@ def resume_instance(project_id: str, zone: str, instance_name: str) -> None: 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 resume. + instance_name: name of the instance you want to resume. """ instance_client = compute_v1.InstancesClient() diff --git a/compute/compute/ingredients/instances/suspend.py b/compute/compute/ingredients/instances/suspend.py index d38cac81e3c..64a15da740e 100644 --- a/compute/compute/ingredients/instances/suspend.py +++ b/compute/compute/ingredients/instances/suspend.py @@ -28,7 +28,7 @@ def suspend_instance(project_id: str, zone: str, instance_name: str) -> None: 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 suspend. + instance_name: name of the instance you want to suspend. """ instance_client = compute_v1.InstancesClient() @@ -36,7 +36,7 @@ def suspend_instance(project_id: str, zone: str, instance_name: str) -> None: project=project_id, zone=zone, instance=instance_name ) - wait_for_extended_operation(operation, "instance suspend") + wait_for_extended_operation(operation, "suspend instance") return # diff --git a/compute/compute/snippets/instances/resume.py b/compute/compute/snippets/instances/resume.py index 715a1681b72..04d08705c93 100644 --- a/compute/compute/snippets/instances/resume.py +++ b/compute/compute/snippets/instances/resume.py @@ -81,7 +81,7 @@ def resume_instance(project_id: str, zone: str, instance_name: str) -> None: 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 resume. + instance_name: name of the instance you want to resume. """ instance_client = compute_v1.InstancesClient() diff --git a/compute/compute/snippets/instances/suspend.py b/compute/compute/snippets/instances/suspend.py index 7dd286a3c4b..4a7c373dafc 100644 --- a/compute/compute/snippets/instances/suspend.py +++ b/compute/compute/snippets/instances/suspend.py @@ -81,7 +81,7 @@ def suspend_instance(project_id: str, zone: str, instance_name: str) -> None: 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 suspend. + instance_name: name of the instance you want to suspend. """ instance_client = compute_v1.InstancesClient() @@ -89,7 +89,7 @@ def suspend_instance(project_id: str, zone: str, instance_name: str) -> None: project=project_id, zone=zone, instance=instance_name ) - wait_for_extended_operation(operation, "instance suspend") + wait_for_extended_operation(operation, "suspend instance") return From 64f3dc55c3876dd7246f514d23f8d0deedee67c9 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Fri, 1 Jul 2022 14:15:28 +0200 Subject: [PATCH 088/113] docs(samples): Adding the missing compute_create_windows_instance_external_ip region (#300) --- .../create_windows_instance.py | 14 ++++++++++---- .../create_windows_instance.py | 2 ++ .../create_windows_instance.py | 4 +++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/compute/compute/ingredients/instances/create_start_instance/create_windows_instance.py b/compute/compute/ingredients/instances/create_start_instance/create_windows_instance.py index 51d139a5fa4..ee437e5c562 100644 --- a/compute/compute/ingredients/instances/create_start_instance/create_windows_instance.py +++ b/compute/compute/ingredients/instances/create_start_instance/create_windows_instance.py @@ -67,9 +67,15 @@ def create_windows_instance(project_id: str, zone: str, instance_name: str, # 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, - ) + 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/compute/recipes/instances/create_start_instance/create_windows_instance.py b/compute/compute/recipes/instances/create_start_instance/create_windows_instance.py index d11fd6fcc18..53f565600e1 100644 --- a/compute/compute/recipes/instances/create_start_instance/create_windows_instance.py +++ b/compute/compute/recipes/instances/create_start_instance/create_windows_instance.py @@ -13,6 +13,7 @@ # limitations under the License. # flake8: noqa +# # # @@ -29,3 +30,4 @@ # # +# diff --git a/compute/compute/snippets/instances/create_start_instance/create_windows_instance.py b/compute/compute/snippets/instances/create_start_instance/create_windows_instance.py index 7ed909d37a6..3d6877fc951 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_windows_instance.py +++ b/compute/compute/snippets/instances/create_start_instance/create_windows_instance.py @@ -19,6 +19,7 @@ # directory and apply your changes there. +# [START compute_create_windows_instance_external_ip] # [START compute_create_windows_instance_internal_ip] import re import sys @@ -305,9 +306,10 @@ def create_windows_instance( machine_type=machine_type, network_link=network_link, subnetwork_link=subnetwork_link, - external_access=True, + 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] From accc34983a3ae521d528633f28006554c7d78c8f Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 6 Jul 2022 13:52:50 +0200 Subject: [PATCH 089/113] docs(samples): Bulk insert sample (#299) Co-authored-by: Anthonios Partheniou Co-authored-by: Owl Bot --- .../ingredients/instances/bulk_insert.py | 90 +++++++++ .../compute/recipes/instances/bulk_insert.py | 40 ++++ .../compute/snippets/instances/bulk_insert.py | 191 ++++++++++++++++++ compute/compute/snippets/tests/test_bulk.py | 76 +++++++ 4 files changed, 397 insertions(+) create mode 100644 compute/compute/ingredients/instances/bulk_insert.py create mode 100644 compute/compute/recipes/instances/bulk_insert.py create mode 100644 compute/compute/snippets/instances/bulk_insert.py create mode 100644 compute/compute/snippets/tests/test_bulk.py diff --git a/compute/compute/ingredients/instances/bulk_insert.py b/compute/compute/ingredients/instances/bulk_insert.py new file mode 100644 index 00000000000..d7cf578d41a --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/bulk_insert.py b/compute/compute/recipes/instances/bulk_insert.py new file mode 100644 index 00000000000..f5bfda9617a --- /dev/null +++ b/compute/compute/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/compute/snippets/instances/bulk_insert.py b/compute/compute/snippets/instances/bulk_insert.py new file mode 100644 index 00000000000..efe095e3e1f --- /dev/null +++ b/compute/compute/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/compute/snippets/tests/test_bulk.py b/compute/compute/snippets/tests/test_bulk.py new file mode 100644 index 00000000000..2d270f1245a --- /dev/null +++ b/compute/compute/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) From 4ec84324681754b6150f214c018ca00ab4815a3a Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Sun, 10 Jul 2022 07:17:36 -0400 Subject: [PATCH 090/113] fix: require python 3.7+ (#304) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(python): drop python 3.6 Source-Link: https://github.com/googleapis/synthtool/commit/4f89b13af10d086458f9b379e56a614f9d6dab7b Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:e7bb19d47c13839fe8c147e50e02e8b6cf5da8edd1af8b82208cd6f66cc2829c * add api_description to .repo-metadata.json * require python 3.7+ in setup.py * remove python 3.6 sample configs * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * exclude templated readme Co-authored-by: Owl Bot Co-authored-by: Maciej Strzelczyk Co-authored-by: Anthonios Partheniou --- compute/compute/noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/noxfile.py b/compute/compute/noxfile.py index a40410b5636..29b5bc85218 100644 --- a/compute/compute/noxfile.py +++ b/compute/compute/noxfile.py @@ -89,7 +89,7 @@ def get_pytest_env_vars() -> Dict[str, str]: # DO NOT EDIT - automatically generated. # All versions used to test samples. -ALL_VERSIONS = ["3.6", "3.7", "3.8", "3.9", "3.10"] +ALL_VERSIONS = ["3.7", "3.8", "3.9", "3.10"] # Any default versions that should be ignored. IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] From 54c352dc7349a4807b24a0abf9840d64169b2bbb Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Thu, 14 Jul 2022 16:32:57 +0200 Subject: [PATCH 091/113] docs(samples): Adding samples for Spot VMs (#285) * docs(samples): Adding samples for Spot VMs * Fixing tests perhaps * Update samples/snippets/instances/spot/__init__.py Co-authored-by: Leah E. Cole <6719667+leahecole@users.noreply.github.com> * Update samples/recipes/instances/spot/__init__.py Co-authored-by: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Co-authored-by: Leah E. Cole <6719667+leahecole@users.noreply.github.com> --- .../ingredients/instances/create_instance.py | 19 +- ...e_instance_from_template_with_overrides.py | 2 +- .../instances/preemptible/create.py | 17 -- .../ingredients/instances/spot/create.py | 43 +++ .../compute/ingredients/instances/spot/get.py | 39 +++ .../recipes/instances/spot/__init__.py | 0 .../compute/recipes/instances/spot/create.py | 31 ++ .../recipes/instances/spot/is_spot_vm.py | 21 ++ compute/compute/snippets/instances/create.py | 22 +- .../create_from_custom_image.py | 22 +- .../create_from_public_image.py | 22 +- .../create_from_snapshot.py | 22 +- .../create_windows_instance.py | 22 +- .../create_with_additional_disk.py | 22 +- .../create_with_existing_disks.py | 22 +- .../create_with_snapshotted_data_disk.py | 22 +- .../snippets/instances/create_with_subnet.py | 22 +- .../instances/custom_hostname/create.py | 22 +- .../create_shared_with_helper.py | 22 +- .../create_with_helper.py | 22 +- .../create_without_helper.py | 22 +- .../extra_mem_no_helper.py | 22 +- .../instances/delete_protection/create.py | 22 +- .../create_from_template_with_overrides.py | 2 +- .../preemptible/create_preemptible.py | 22 +- .../snippets/instances/spot/__init__.py | 0 .../compute/snippets/instances/spot/create.py | 287 ++++++++++++++++++ .../snippets/instances/spot/is_spot_vm.py | 46 +++ compute/compute/snippets/tests/test_images.py | 2 +- .../compute/snippets/tests/test_spot_vms.py | 42 +++ 30 files changed, 832 insertions(+), 71 deletions(-) create mode 100644 compute/compute/ingredients/instances/spot/create.py create mode 100644 compute/compute/ingredients/instances/spot/get.py create mode 100644 compute/compute/recipes/instances/spot/__init__.py create mode 100644 compute/compute/recipes/instances/spot/create.py create mode 100644 compute/compute/recipes/instances/spot/is_spot_vm.py create mode 100644 compute/compute/snippets/instances/spot/__init__.py create mode 100644 compute/compute/snippets/instances/spot/create.py create mode 100644 compute/compute/snippets/instances/spot/is_spot_vm.py create mode 100644 compute/compute/snippets/tests/test_spot_vms.py diff --git a/compute/compute/ingredients/instances/create_instance.py b/compute/compute/ingredients/instances/create_instance.py index f49fa6a981a..afbbbef330a 100644 --- a/compute/compute/ingredients/instances/create_instance.py +++ b/compute/compute/ingredients/instances/create_instance.py @@ -19,6 +19,7 @@ import re from typing import List +import warnings from google.cloud import compute_v1 @@ -37,6 +38,8 @@ def create_instance( 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: @@ -69,7 +72,10 @@ def create_instance( 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. + 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 @@ -97,8 +103,10 @@ def create_instance( 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): @@ -109,13 +117,18 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/ingredients/instances/create_instance_from_template_with_overrides.py b/compute/compute/ingredients/instances/create_instance_from_template_with_overrides.py index 5920a06482a..39432712b92 100644 --- a/compute/compute/ingredients/instances/create_instance_from_template_with_overrides.py +++ b/compute/compute/ingredients/instances/create_instance_from_template_with_overrides.py @@ -72,7 +72,7 @@ def create_instance_from_template_with_overrides( instance = compute_v1.Instance() instance.name = instance_name instance.machine_type = machine_type - instance.disks = instance_template.properties.disks + instance.disks = list(instance_template.properties.disks) new_disk = compute_v1.AttachedDisk() new_disk.initialize_params.disk_size_gb = 50 diff --git a/compute/compute/ingredients/instances/preemptible/create.py b/compute/compute/ingredients/instances/preemptible/create.py index 46d611a5dca..145fbc3679f 100644 --- a/compute/compute/ingredients/instances/preemptible/create.py +++ b/compute/compute/ingredients/instances/preemptible/create.py @@ -12,23 +12,6 @@ # 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 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT 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. diff --git a/compute/compute/ingredients/instances/spot/create.py b/compute/compute/ingredients/instances/spot/create.py new file mode 100644 index 00000000000..ba87875cf8e --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/spot/get.py b/compute/compute/ingredients/instances/spot/get.py new file mode 100644 index 00000000000..3366cf36d59 --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/spot/__init__.py b/compute/compute/recipes/instances/spot/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/recipes/instances/spot/create.py b/compute/compute/recipes/instances/spot/create.py new file mode 100644 index 00000000000..fba16e4ce8a --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/spot/is_spot_vm.py b/compute/compute/recipes/instances/spot/is_spot_vm.py new file mode 100644 index 00000000000..ea613650ab0 --- /dev/null +++ b/compute/compute/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/compute/snippets/instances/create.py b/compute/compute/snippets/instances/create.py index e538b5b59a2..801c05b16a8 100644 --- a/compute/compute/snippets/instances/create.py +++ b/compute/compute/snippets/instances/create.py @@ -23,6 +23,7 @@ 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 @@ -143,6 +144,8 @@ def create_instance( 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: @@ -175,7 +178,10 @@ def create_instance( 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. + 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 @@ -205,6 +211,7 @@ def create_instance( # 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): @@ -215,13 +222,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py b/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py index 4ddc4138f3d..d78dd819868 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py @@ -23,6 +23,7 @@ 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 @@ -143,6 +144,8 @@ def create_instance( 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: @@ -175,7 +178,10 @@ def create_instance( 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. + 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 @@ -205,6 +211,7 @@ def create_instance( # 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): @@ -215,13 +222,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py b/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py index 439d985410e..50bf6bffdc9 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py @@ -23,6 +23,7 @@ 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 @@ -143,6 +144,8 @@ def create_instance( 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: @@ -175,7 +178,10 @@ def create_instance( 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. + 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 @@ -205,6 +211,7 @@ def create_instance( # 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): @@ -215,13 +222,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py b/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py index a3dde1bf898..d71bd4500f9 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py +++ b/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py @@ -23,6 +23,7 @@ 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 @@ -125,6 +126,8 @@ def create_instance( 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: @@ -157,7 +160,10 @@ def create_instance( 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. + 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 @@ -187,6 +193,7 @@ def create_instance( # 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): @@ -197,13 +204,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/create_start_instance/create_windows_instance.py b/compute/compute/snippets/instances/create_start_instance/create_windows_instance.py index 3d6877fc951..ce0620da216 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_windows_instance.py +++ b/compute/compute/snippets/instances/create_start_instance/create_windows_instance.py @@ -24,6 +24,7 @@ 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 @@ -144,6 +145,8 @@ def create_instance( 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: @@ -176,7 +179,10 @@ def create_instance( 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. + 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 @@ -206,6 +212,7 @@ def create_instance( # 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): @@ -216,13 +223,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py b/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py index e923f73624a..e75aadccd78 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py @@ -23,6 +23,7 @@ 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 @@ -173,6 +174,8 @@ def create_instance( 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: @@ -205,7 +208,10 @@ def create_instance( 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. + 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 @@ -235,6 +241,7 @@ def create_instance( # 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): @@ -245,13 +252,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py b/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py index 22f92da6073..4fa4cd2e34c 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py @@ -23,6 +23,7 @@ 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 @@ -101,6 +102,8 @@ def create_instance( 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: @@ -133,7 +136,10 @@ def create_instance( 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. + 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 @@ -163,6 +169,7 @@ def create_instance( # 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): @@ -173,13 +180,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py b/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py index c86108b406f..057eba548e6 100644 --- a/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py +++ b/compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py @@ -23,6 +23,7 @@ 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 @@ -180,6 +181,8 @@ def create_instance( 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: @@ -212,7 +215,10 @@ def create_instance( 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. + 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 @@ -242,6 +248,7 @@ def create_instance( # 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): @@ -252,13 +259,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/create_with_subnet.py b/compute/compute/snippets/instances/create_with_subnet.py index 4ab870837ea..6696dc4a198 100644 --- a/compute/compute/snippets/instances/create_with_subnet.py +++ b/compute/compute/snippets/instances/create_with_subnet.py @@ -23,6 +23,7 @@ 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 @@ -143,6 +144,8 @@ def create_instance( 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: @@ -175,7 +178,10 @@ def create_instance( 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. + 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 @@ -205,6 +211,7 @@ def create_instance( # 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): @@ -215,13 +222,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/custom_hostname/create.py b/compute/compute/snippets/instances/custom_hostname/create.py index 03498dec9ac..6813bc4dcd5 100644 --- a/compute/compute/snippets/instances/custom_hostname/create.py +++ b/compute/compute/snippets/instances/custom_hostname/create.py @@ -23,6 +23,7 @@ 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 @@ -143,6 +144,8 @@ def create_instance( 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: @@ -175,7 +178,10 @@ def create_instance( 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. + 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 @@ -205,6 +211,7 @@ def create_instance( # 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): @@ -215,13 +222,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py index 9d41d09819c..99dea3ef2da 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py @@ -26,6 +26,7 @@ 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 @@ -337,6 +338,8 @@ def create_instance( 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: @@ -369,7 +372,10 @@ def create_instance( 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. + 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 @@ -399,6 +405,7 @@ def create_instance( # 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): @@ -409,13 +416,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py index cdb85464ebe..18f642549ca 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py @@ -26,6 +26,7 @@ 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 @@ -337,6 +338,8 @@ def create_instance( 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: @@ -369,7 +372,10 @@ def create_instance( 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. + 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 @@ -399,6 +405,7 @@ def create_instance( # 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): @@ -409,13 +416,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py b/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py index 42da51444cc..30b2edd9e9a 100644 --- a/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py @@ -23,6 +23,7 @@ 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 @@ -143,6 +144,8 @@ def create_instance( 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: @@ -175,7 +178,10 @@ def create_instance( 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. + 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 @@ -205,6 +211,7 @@ def create_instance( # 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): @@ -215,13 +222,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py b/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py index de1c0057362..de210032724 100644 --- a/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py +++ b/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py @@ -23,6 +23,7 @@ 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 @@ -143,6 +144,8 @@ def create_instance( 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: @@ -175,7 +178,10 @@ def create_instance( 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. + 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 @@ -205,6 +211,7 @@ def create_instance( # 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): @@ -215,13 +222,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/delete_protection/create.py b/compute/compute/snippets/instances/delete_protection/create.py index addf7a9c775..7d9bbe9bbfa 100644 --- a/compute/compute/snippets/instances/delete_protection/create.py +++ b/compute/compute/snippets/instances/delete_protection/create.py @@ -23,6 +23,7 @@ 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 @@ -143,6 +144,8 @@ def create_instance( 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: @@ -175,7 +178,10 @@ def create_instance( 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. + 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 @@ -205,6 +211,7 @@ def create_instance( # 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): @@ -215,13 +222,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py b/compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py index da62d3ecf87..9ff3ba4af4f 100644 --- a/compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py +++ b/compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py @@ -125,7 +125,7 @@ def create_instance_from_template_with_overrides( instance = compute_v1.Instance() instance.name = instance_name instance.machine_type = machine_type - instance.disks = instance_template.properties.disks + instance.disks = list(instance_template.properties.disks) new_disk = compute_v1.AttachedDisk() new_disk.initialize_params.disk_size_gb = 50 diff --git a/compute/compute/snippets/instances/preemptible/create_preemptible.py b/compute/compute/snippets/instances/preemptible/create_preemptible.py index a85f0135501..5816d39f4b0 100644 --- a/compute/compute/snippets/instances/preemptible/create_preemptible.py +++ b/compute/compute/snippets/instances/preemptible/create_preemptible.py @@ -23,6 +23,7 @@ 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 @@ -143,6 +144,8 @@ def create_instance( 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: @@ -175,7 +178,10 @@ def create_instance( 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. + 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 @@ -205,6 +211,7 @@ def create_instance( # 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): @@ -215,13 +222,22 @@ def create_instance( if accelerators: instance.guest_accelerators = accelerators - instance.network_interfaces = [network_interface] - 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 diff --git a/compute/compute/snippets/instances/spot/__init__.py b/compute/compute/snippets/instances/spot/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compute/compute/snippets/instances/spot/create.py b/compute/compute/snippets/instances/spot/create.py new file mode 100644 index 00000000000..ddf437f6bfc --- /dev/null +++ b/compute/compute/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/compute/snippets/instances/spot/is_spot_vm.py b/compute/compute/snippets/instances/spot/is_spot_vm.py new file mode 100644 index 00000000000..4cb83657094 --- /dev/null +++ b/compute/compute/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/compute/snippets/tests/test_images.py b/compute/compute/snippets/tests/test_images.py index b6f61b3c821..1cc7e6bb58f 100644 --- a/compute/compute/snippets/tests/test_images.py +++ b/compute/compute/snippets/tests/test_images.py @@ -96,7 +96,7 @@ def test_get_image(): image2 = get_image("debian-cloud", image.name) - assert image == image2 + assert image.name == image2.name def test_create_delete_image(test_disk): diff --git a/compute/compute/snippets/tests/test_spot_vms.py b/compute/compute/snippets/tests/test_spot_vms.py new file mode 100644 index 00000000000..5e6114161ca --- /dev/null +++ b/compute/compute/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) From abc2477c026b0f30b4167b43814500fd81bd825e Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Sat, 16 Jul 2022 16:52:33 +0200 Subject: [PATCH 092/113] chore(deps): update all dependencies (#293) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * revert Co-authored-by: Owl Bot Co-authored-by: Maciej Strzelczyk Co-authored-by: Anthonios Partheniou --- compute/compute/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index 340112169b5..d759b1ec262 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 black==22.3.0 -google-cloud-compute==1.3.1 +google-cloud-compute==1.3.2 From ad92de6bd141ea9b81cbcd82bae371b0a2575b96 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Mon, 18 Jul 2022 14:26:51 +0200 Subject: [PATCH 093/113] docs(samples): adding sample for local SSD disk (#294) * docs(samples): adding sample for local SSD disk * Updating region tag * Regenerating * Updating snippets Co-authored-by: Anthonios Partheniou --- .../compute/ingredients/disks/local_ssd.py | 41 +++ .../create_with_local_ssd.py | 43 +++ .../create_with_local_ssd.py | 32 ++ .../create_with_local_ssd.py | 310 ++++++++++++++++++ .../compute/snippets/tests/test_create_vm.py | 15 + 5 files changed, 441 insertions(+) create mode 100644 compute/compute/ingredients/disks/local_ssd.py create mode 100644 compute/compute/ingredients/instances/create_start_instance/create_with_local_ssd.py create mode 100644 compute/compute/recipes/instances/create_start_instance/create_with_local_ssd.py create mode 100644 compute/compute/snippets/instances/create_start_instance/create_with_local_ssd.py diff --git a/compute/compute/ingredients/disks/local_ssd.py b/compute/compute/ingredients/disks/local_ssd.py new file mode 100644 index 00000000000..8b7f6f54d34 --- /dev/null +++ b/compute/compute/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/compute/ingredients/instances/create_start_instance/create_with_local_ssd.py b/compute/compute/ingredients/instances/create_start_instance/create_with_local_ssd.py new file mode 100644 index 00000000000..9802a1552d1 --- /dev/null +++ b/compute/compute/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/compute/recipes/instances/create_start_instance/create_with_local_ssd.py b/compute/compute/recipes/instances/create_start_instance/create_with_local_ssd.py new file mode 100644 index 00000000000..853a58e1686 --- /dev/null +++ b/compute/compute/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/compute/snippets/instances/create_start_instance/create_with_local_ssd.py b/compute/compute/snippets/instances/create_start_instance/create_with_local_ssd.py new file mode 100644 index 00000000000..c8038abd276 --- /dev/null +++ b/compute/compute/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/compute/snippets/tests/test_create_vm.py b/compute/compute/snippets/tests/test_create_vm.py index 58382ed924e..5a2235efe0d 100644 --- a/compute/compute/snippets/tests/test_create_vm.py +++ b/compute/compute/snippets/tests/test_create_vm.py @@ -32,6 +32,7 @@ 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, ) @@ -239,3 +240,17 @@ def test_create_with_existing_disks(boot_disk, empty_disk): 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) From 435fa3541cb47999914fd38deed1d5b91a269ce2 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Tue, 19 Jul 2022 14:07:46 +0200 Subject: [PATCH 094/113] chore(tests): Request id test (#298) * chore(tests): Request id test * Fixing test formatting. Co-authored-by: Anthonios Partheniou --- .../compute/snippets/tests/test_request_id.py | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 compute/compute/snippets/tests/test_request_id.py diff --git a/compute/compute/snippets/tests/test_request_id.py b/compute/compute/snippets/tests/test_request_id.py new file mode 100644 index 00000000000..6266dd2c21e --- /dev/null +++ b/compute/compute/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 samples.snippets.instances.create_start_instance.create_windows_instance import \ + get_image_from_family + +from ..disks.delete import delete_disk +from ..disks.list import list_disks + +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) From ab5e2579d1d3c6be3133a964bec9eec98582e77e Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Thu, 4 Aug 2022 17:53:26 +0200 Subject: [PATCH 095/113] chore(deps): update all dependencies (#310) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * revert * lint Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- .../create_start_instance/create_with_existing_disks.py | 2 +- compute/compute/requirements-test.txt | 2 +- compute/compute/requirements.txt | 4 ++-- compute/compute/snippets/tests/test_disks.py | 6 +++--- compute/compute/snippets/tests/test_snapshots.py | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/compute/compute/ingredients/instances/create_start_instance/create_with_existing_disks.py b/compute/compute/ingredients/instances/create_start_instance/create_with_existing_disks.py index 2affc851d47..c4b90a62f93 100644 --- a/compute/compute/ingredients/instances/create_start_instance/create_with_existing_disks.py +++ b/compute/compute/ingredients/instances/create_start_instance/create_with_existing_disks.py @@ -37,7 +37,7 @@ def create_with_existing_disks(project_id: str, zone: str, instance_name: str, d Returns: Instance object. """ - assert(len(disk_names) >= 1) + 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: diff --git a/compute/compute/requirements-test.txt b/compute/compute/requirements-test.txt index db25a383971..bbbe35ef9b3 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -1,4 +1,4 @@ pytest==7.1.2 pytest-parallel==0.1.1 flaky==3.7.0 -google-cloud-storage==2.3.0 +google-cloud-storage==2.4.0 diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index d759b1ec262..97e6e4842f8 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 -black==22.3.0 -google-cloud-compute==1.3.2 +black==22.6.0 +google-cloud-compute==1.4.0 diff --git a/compute/compute/snippets/tests/test_disks.py b/compute/compute/snippets/tests/test_disks.py index e0d3675543c..b847bc206ef 100644 --- a/compute/compute/snippets/tests/test_disks.py +++ b/compute/compute/snippets/tests/test_disks.py @@ -42,9 +42,9 @@ def test_disk_create_delete(autodelete_disk_name): 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) + 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: diff --git a/compute/compute/snippets/tests/test_snapshots.py b/compute/compute/snippets/tests/test_snapshots.py index 7e52dcb227a..b62978788e3 100644 --- a/compute/compute/snippets/tests/test_snapshots.py +++ b/compute/compute/snippets/tests/test_snapshots.py @@ -45,8 +45,8 @@ def test_disk(): 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) + 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 From 792cc4ade1831c3b7f2c5223e7a4d05212c3e3ba Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Thu, 4 Aug 2022 19:41:12 +0200 Subject: [PATCH 096/113] chore(deps): update all dependencies (#313) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update all dependencies * revert * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Anthonios Partheniou Co-authored-by: Owl Bot --- compute/compute/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements-test.txt b/compute/compute/requirements-test.txt index bbbe35ef9b3..d666116a4c0 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -1,4 +1,4 @@ pytest==7.1.2 pytest-parallel==0.1.1 flaky==3.7.0 -google-cloud-storage==2.4.0 +google-cloud-storage==2.5.0 From affdebc4491524816622b99156948f724ad41682 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 10 Aug 2022 15:47:37 +0200 Subject: [PATCH 097/113] docs(samples): Various ways of creating a new disk (#308) * docs(samples): Samples for various ways to create disks Co-authored-by: Anthonios Partheniou Co-authored-by: Savija Vijayaraghavan --- .../ingredients/disks/clone_encrypted_disk.py | 64 +++++++++ .../disks/clone_encrypted_disk_managed_key.py | 65 +++++++++ .../ingredients/disks/create_from_snapshot.py | 1 - .../ingredients/disks/create_from_source.py | 55 ++++++++ .../disks/create_kms_encrypted_disk.py | 70 ++++++++++ .../disks/regional_create_from_source.py | 68 +++++++++ .../ingredients/disks/regional_delete.py | 39 ++++++ .../recipes/disks/clone_encrypted_disk.py | 23 ++++ .../disks/clone_encrypted_disk_managed_key.py | 23 ++++ .../recipes/disks/create_from_source.py | 23 ++++ .../disks/create_kms_encrypted_disk.py | 24 ++++ .../disks/regional_create_from_source.py | 23 ++++ .../compute/recipes/disks/regional_delete.py | 24 ++++ compute/compute/requirements-test.txt | 1 + .../snippets/disks/clone_encrypted_disk.py | 124 +++++++++++++++++ .../disks/clone_encrypted_disk_managed_key.py | 125 +++++++++++++++++ .../snippets/disks/create_from_source.py | 116 ++++++++++++++++ .../disks/create_kms_encrypted_disk.py | 130 ++++++++++++++++++ .../disks/regional_create_from_source.py | 129 +++++++++++++++++ .../compute/snippets/disks/regional_delete.py | 92 +++++++++++++ compute/compute/snippets/tests/test_disks.py | 116 ++++++++++++++++ .../tests/test_instance_from_template.py | 1 + .../tests/test_instance_start_stop.py | 21 +++ 23 files changed, 1356 insertions(+), 1 deletion(-) create mode 100644 compute/compute/ingredients/disks/clone_encrypted_disk.py create mode 100644 compute/compute/ingredients/disks/clone_encrypted_disk_managed_key.py create mode 100644 compute/compute/ingredients/disks/create_from_source.py create mode 100644 compute/compute/ingredients/disks/create_kms_encrypted_disk.py create mode 100644 compute/compute/ingredients/disks/regional_create_from_source.py create mode 100644 compute/compute/ingredients/disks/regional_delete.py create mode 100644 compute/compute/recipes/disks/clone_encrypted_disk.py create mode 100644 compute/compute/recipes/disks/clone_encrypted_disk_managed_key.py create mode 100644 compute/compute/recipes/disks/create_from_source.py create mode 100644 compute/compute/recipes/disks/create_kms_encrypted_disk.py create mode 100644 compute/compute/recipes/disks/regional_create_from_source.py create mode 100644 compute/compute/recipes/disks/regional_delete.py create mode 100644 compute/compute/snippets/disks/clone_encrypted_disk.py create mode 100644 compute/compute/snippets/disks/clone_encrypted_disk_managed_key.py create mode 100644 compute/compute/snippets/disks/create_from_source.py create mode 100644 compute/compute/snippets/disks/create_kms_encrypted_disk.py create mode 100644 compute/compute/snippets/disks/regional_create_from_source.py create mode 100644 compute/compute/snippets/disks/regional_delete.py diff --git a/compute/compute/ingredients/disks/clone_encrypted_disk.py b/compute/compute/ingredients/disks/clone_encrypted_disk.py new file mode 100644 index 00000000000..2df729de693 --- /dev/null +++ b/compute/compute/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/compute/ingredients/disks/clone_encrypted_disk_managed_key.py b/compute/compute/ingredients/disks/clone_encrypted_disk_managed_key.py new file mode 100644 index 00000000000..53ab7baa5a1 --- /dev/null +++ b/compute/compute/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/compute/ingredients/disks/create_from_snapshot.py b/compute/compute/ingredients/disks/create_from_snapshot.py index 88a79751204..a2dd1f49595 100644 --- a/compute/compute/ingredients/disks/create_from_snapshot.py +++ b/compute/compute/ingredients/disks/create_from_snapshot.py @@ -16,7 +16,6 @@ # 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 diff --git a/compute/compute/ingredients/disks/create_from_source.py b/compute/compute/ingredients/disks/create_from_source.py new file mode 100644 index 00000000000..a7ed3006ceb --- /dev/null +++ b/compute/compute/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/compute/ingredients/disks/create_kms_encrypted_disk.py b/compute/compute/ingredients/disks/create_kms_encrypted_disk.py new file mode 100644 index 00000000000..7078ea6b6b6 --- /dev/null +++ b/compute/compute/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/compute/ingredients/disks/regional_create_from_source.py b/compute/compute/ingredients/disks/regional_create_from_source.py new file mode 100644 index 00000000000..af6757d7efb --- /dev/null +++ b/compute/compute/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/compute/ingredients/disks/regional_delete.py b/compute/compute/ingredients/disks/regional_delete.py new file mode 100644 index 00000000000..0bc2f59d87a --- /dev/null +++ b/compute/compute/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/compute/recipes/disks/clone_encrypted_disk.py b/compute/compute/recipes/disks/clone_encrypted_disk.py new file mode 100644 index 00000000000..9ce0ddf795d --- /dev/null +++ b/compute/compute/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/compute/recipes/disks/clone_encrypted_disk_managed_key.py b/compute/compute/recipes/disks/clone_encrypted_disk_managed_key.py new file mode 100644 index 00000000000..d4b100af644 --- /dev/null +++ b/compute/compute/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/compute/recipes/disks/create_from_source.py b/compute/compute/recipes/disks/create_from_source.py new file mode 100644 index 00000000000..f40f561ec3f --- /dev/null +++ b/compute/compute/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/compute/recipes/disks/create_kms_encrypted_disk.py b/compute/compute/recipes/disks/create_kms_encrypted_disk.py new file mode 100644 index 00000000000..06cc0d049d7 --- /dev/null +++ b/compute/compute/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/compute/recipes/disks/regional_create_from_source.py b/compute/compute/recipes/disks/regional_create_from_source.py new file mode 100644 index 00000000000..aceeaa99c9f --- /dev/null +++ b/compute/compute/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/compute/recipes/disks/regional_delete.py b/compute/compute/recipes/disks/regional_delete.py new file mode 100644 index 00000000000..986f7dfcda6 --- /dev/null +++ b/compute/compute/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/compute/requirements-test.txt b/compute/compute/requirements-test.txt index d666116a4c0..d7c6a629cf0 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -2,3 +2,4 @@ pytest==7.1.2 pytest-parallel==0.1.1 flaky==3.7.0 google-cloud-storage==2.5.0 +google-cloud-kms==2.12.0 diff --git a/compute/compute/snippets/disks/clone_encrypted_disk.py b/compute/compute/snippets/disks/clone_encrypted_disk.py new file mode 100644 index 00000000000..5850656e2b8 --- /dev/null +++ b/compute/compute/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/compute/snippets/disks/clone_encrypted_disk_managed_key.py b/compute/compute/snippets/disks/clone_encrypted_disk_managed_key.py new file mode 100644 index 00000000000..8f89d191be5 --- /dev/null +++ b/compute/compute/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/compute/snippets/disks/create_from_source.py b/compute/compute/snippets/disks/create_from_source.py new file mode 100644 index 00000000000..85a1d38254e --- /dev/null +++ b/compute/compute/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/compute/snippets/disks/create_kms_encrypted_disk.py b/compute/compute/snippets/disks/create_kms_encrypted_disk.py new file mode 100644 index 00000000000..d3f64377a16 --- /dev/null +++ b/compute/compute/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/compute/snippets/disks/regional_create_from_source.py b/compute/compute/snippets/disks/regional_create_from_source.py new file mode 100644 index 00000000000..25a682b391c --- /dev/null +++ b/compute/compute/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/compute/snippets/disks/regional_delete.py b/compute/compute/snippets/disks/regional_delete.py new file mode 100644 index 00000000000..b68bbd96981 --- /dev/null +++ b/compute/compute/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/compute/snippets/tests/test_disks.py b/compute/compute/snippets/tests/test_disks.py index b847bc206ef..75fa2e9867b 100644 --- a/compute/compute/snippets/tests/test_disks.py +++ b/compute/compute/snippets/tests/test_disks.py @@ -15,15 +15,76 @@ 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() @@ -37,6 +98,19 @@ def autodelete_disk_name(): 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') @@ -57,3 +131,45 @@ def test_disk_create_delete(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/compute/snippets/tests/test_instance_from_template.py b/compute/compute/snippets/tests/test_instance_from_template.py index e322082581b..e8a20f372d8 100644 --- a/compute/compute/snippets/tests/test_instance_from_template.py +++ b/compute/compute/snippets/tests/test_instance_from_template.py @@ -37,6 +37,7 @@ def instance_template(): "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 diff --git a/compute/compute/snippets/tests/test_instance_start_stop.py b/compute/compute/snippets/tests/test_instance_start_stop.py index 737400f8a64..8c6efc802de 100644 --- a/compute/compute/snippets/tests/test_instance_start_stop.py +++ b/compute/compute/snippets/tests/test_instance_start_stop.py @@ -21,6 +21,8 @@ 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 @@ -135,6 +137,13 @@ def compute_encrypted_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" @@ -166,3 +175,15 @@ def test_instance_encrypted(compute_encrypted_instance): 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 From f99074e05eba7e3357e01226a3c4da35dd76b67b Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 12 Aug 2022 14:51:08 +0200 Subject: [PATCH 098/113] chore(deps): update all dependencies to v1.5.0 (#317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update all dependencies to v1.5.0 * revert * update system test to reflect exception message * cater for protobuf 3.x.x * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Anthonios Partheniou Co-authored-by: Owl Bot --- compute/compute/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index 97e6e4842f8..6d0c43e1eed 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 black==22.6.0 -google-cloud-compute==1.4.0 +google-cloud-compute==1.5.0 From 7b0bcb8615f524e6fa38f1d8d5c06faf72784f28 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 19 Aug 2022 18:32:33 +0200 Subject: [PATCH 099/113] chore(deps): update all dependencies (#324) --- compute/compute/requirements-test.txt | 2 +- compute/compute/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compute/compute/requirements-test.txt b/compute/compute/requirements-test.txt index d7c6a629cf0..b9b7ab7c9df 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -2,4 +2,4 @@ pytest==7.1.2 pytest-parallel==0.1.1 flaky==3.7.0 google-cloud-storage==2.5.0 -google-cloud-kms==2.12.0 +google-cloud-kms==2.12.1 diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index 6d0c43e1eed..2eed6c0c178 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 black==22.6.0 -google-cloud-compute==1.5.0 +google-cloud-compute==1.5.1 From e0008591927aa05e34b310942263c6be89fd7614 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Thu, 1 Sep 2022 15:50:30 +0200 Subject: [PATCH 100/113] chore(deps): update dependency black to v22.8.0 (#331) --- compute/compute/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index 2eed6c0c178..0f34ab3d89e 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 -black==22.6.0 +black==22.8.0 google-cloud-compute==1.5.1 From fc767a98b880c1aae165245f652248b5df3fcba0 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 6 Sep 2022 19:37:49 +0200 Subject: [PATCH 101/113] chore(deps): update dependency pytest to v7.1.3 (#334) Co-authored-by: Anthonios Partheniou --- compute/compute/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements-test.txt b/compute/compute/requirements-test.txt index b9b7ab7c9df..d527da3239d 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -1,4 +1,4 @@ -pytest==7.1.2 +pytest==7.1.3 pytest-parallel==0.1.1 flaky==3.7.0 google-cloud-storage==2.5.0 From 239ab996418e0f6acd92a902c8fc2a19558afe7d Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 13 Sep 2022 17:21:22 +0200 Subject: [PATCH 102/113] chore(deps): update dependency google-cloud-compute to v1.5.2 (#339) Co-authored-by: Anthonios Partheniou --- compute/compute/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index 0f34ab3d89e..38d877669b8 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 black==22.8.0 -google-cloud-compute==1.5.1 +google-cloud-compute==1.5.2 From a01f13516dbba33ca0a9c08c25410d67632cf02d Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Tue, 13 Sep 2022 16:42:16 +0000 Subject: [PATCH 103/113] chore: detect samples tests in nested directories (#343) Source-Link: https://github.com/googleapis/synthtool/commit/50db768f450a50d7c1fd62513c113c9bb96fd434 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:e09366bdf0fd9c8976592988390b24d53583dd9f002d476934da43725adbb978 --- compute/compute/noxfile.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compute/compute/noxfile.py b/compute/compute/noxfile.py index 29b5bc85218..b053ca568f6 100644 --- a/compute/compute/noxfile.py +++ b/compute/compute/noxfile.py @@ -208,8 +208,10 @@ def _session_tests( session: nox.sessions.Session, post_install: Callable = None ) -> None: # check for presence of tests - test_list = glob.glob("*_test.py") + glob.glob("test_*.py") - test_list.extend(glob.glob("tests")) + test_list = glob.glob("**/*_test.py", recursive=True) + glob.glob( + "**/test_*.py", recursive=True + ) + test_list.extend(glob.glob("**/tests", recursive=True)) if len(test_list) == 0: print("No tests found, skipping directory.") From 8a35f22bcc972d9375f0e39bc3b428244fe2220c Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 4 Oct 2022 15:32:11 +0200 Subject: [PATCH 104/113] chore(deps): update all dependencies (#348) --- compute/compute/requirements-test.txt | 2 +- compute/compute/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compute/compute/requirements-test.txt b/compute/compute/requirements-test.txt index d527da3239d..295ed200889 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -2,4 +2,4 @@ pytest==7.1.3 pytest-parallel==0.1.1 flaky==3.7.0 google-cloud-storage==2.5.0 -google-cloud-kms==2.12.1 +google-cloud-kms==2.12.2 diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index 38d877669b8..4bfdd897dae 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 black==22.8.0 -google-cloud-compute==1.5.2 +google-cloud-compute==1.6.0 From 7e0f35bcc93a84a644bbba5f3a94b2b5e81a8bda Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 5 Oct 2022 15:19:38 +0200 Subject: [PATCH 105/113] docs(samples): Fixing an issue with listing all instances (#349) --- compute/compute/ingredients/instances/list_all.py | 8 ++++---- compute/compute/snippets/instances/list_all.py | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compute/compute/ingredients/instances/list_all.py b/compute/compute/ingredients/instances/list_all.py index ced8e7a1f27..60498c4df67 100644 --- a/compute/compute/ingredients/instances/list_all.py +++ b/compute/compute/ingredients/instances/list_all.py @@ -12,10 +12,11 @@ # 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 +# 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 @@ -42,17 +43,16 @@ def list_all_instances( agg_list = instance_client.aggregated_list(request=request) - all_instances = {} + 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] = 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/compute/snippets/instances/list_all.py b/compute/compute/snippets/instances/list_all.py index 3bfda0b9a3c..47302fe423a 100644 --- a/compute/compute/snippets/instances/list_all.py +++ b/compute/compute/snippets/instances/list_all.py @@ -20,6 +20,7 @@ # [START compute_instances_list_all] +from collections import defaultdict from typing import Dict, Iterable from google.cloud import compute_v1 @@ -45,14 +46,14 @@ def list_all_instances( agg_list = instance_client.aggregated_list(request=request) - all_instances = {} + 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] = response.instances + all_instances[zone].extend(response.instances) print(f" {zone}:") for instance in response.instances: print(f" - {instance.name} ({instance.machine_type})") From fc3390b6f13e214e26269db01d52b064f38420fa Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Sat, 8 Oct 2022 19:50:57 +0200 Subject: [PATCH 106/113] chore(deps): update dependency black to v22.10.0 (#352) --- compute/compute/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index 4bfdd897dae..91e28ea3bf7 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 -black==22.8.0 +black==22.10.0 google-cloud-compute==1.6.0 From 7d4074a258281f63eaff355ccd10e66e5a344e1a Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 18 Oct 2022 00:42:56 +0200 Subject: [PATCH 107/113] chore(deps): update all dependencies (#353) --- compute/compute/requirements-test.txt | 2 +- compute/compute/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compute/compute/requirements-test.txt b/compute/compute/requirements-test.txt index 295ed200889..50c818378ba 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -2,4 +2,4 @@ pytest==7.1.3 pytest-parallel==0.1.1 flaky==3.7.0 google-cloud-storage==2.5.0 -google-cloud-kms==2.12.2 +google-cloud-kms==2.12.3 diff --git a/compute/compute/requirements.txt b/compute/compute/requirements.txt index 91e28ea3bf7..0de8929bd5e 100644 --- a/compute/compute/requirements.txt +++ b/compute/compute/requirements.txt @@ -1,3 +1,3 @@ isort==5.10.1 black==22.10.0 -google-cloud-compute==1.6.0 +google-cloud-compute==1.6.1 From 4290d6b19897d9202fc8d0592baa13bfb70d61f5 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Wed, 26 Oct 2022 15:52:42 +0200 Subject: [PATCH 108/113] chore(deps): update dependency pytest to v7.2.0 (#357) * chore(deps): update dependency pytest to v7.2.0 * add missing test dependency `py` Co-authored-by: Anthonios Partheniou --- compute/compute/requirements-test.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compute/compute/requirements-test.txt b/compute/compute/requirements-test.txt index 50c818378ba..1a35c12716c 100644 --- a/compute/compute/requirements-test.txt +++ b/compute/compute/requirements-test.txt @@ -1,5 +1,6 @@ -pytest==7.1.3 +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 From b115165b3bbfb0e3838078e95835925bf1fd873d Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 9 Nov 2022 15:59:26 +0100 Subject: [PATCH 109/113] Removing noxfile.py --- compute/compute/noxfile.py | 313 ------------------------------------- 1 file changed, 313 deletions(-) delete mode 100644 compute/compute/noxfile.py diff --git a/compute/compute/noxfile.py b/compute/compute/noxfile.py deleted file mode 100644 index b053ca568f6..00000000000 --- a/compute/compute/noxfile.py +++ /dev/null @@ -1,313 +0,0 @@ -# Copyright 2019 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT 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 __future__ import print_function - -import glob -import os -from pathlib import Path -import sys -from typing import Callable, Dict, List, Optional - -import nox - - -# WARNING - WARNING - WARNING - WARNING - WARNING -# WARNING - WARNING - WARNING - WARNING - WARNING -# DO NOT EDIT THIS FILE EVER! -# WARNING - WARNING - WARNING - WARNING - WARNING -# WARNING - WARNING - WARNING - WARNING - WARNING - -BLACK_VERSION = "black==22.3.0" -ISORT_VERSION = "isort==5.10.1" - -# Copy `noxfile_config.py` to your directory and modify it instead. - -# `TEST_CONFIG` dict is a configuration hook that allows users to -# modify the test configurations. The values here should be in sync -# with `noxfile_config.py`. Users will copy `noxfile_config.py` into -# their directory and modify it. - -TEST_CONFIG = { - # You can opt out from the test for specific Python versions. - "ignored_versions": [], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # If you need to use a specific version of pip, - # change pip_version_override to the string representation - # of the version number, for example, "20.2.4" - "pip_version_override": None, - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} - - -try: - # Ensure we can import noxfile_config in the project's directory. - sys.path.append(".") - from noxfile_config import TEST_CONFIG_OVERRIDE -except ImportError as e: - print("No user noxfile_config found: detail: {}".format(e)) - TEST_CONFIG_OVERRIDE = {} - -# Update the TEST_CONFIG with the user supplied values. -TEST_CONFIG.update(TEST_CONFIG_OVERRIDE) - - -def get_pytest_env_vars() -> Dict[str, str]: - """Returns a dict for pytest invocation.""" - ret = {} - - # Override the GCLOUD_PROJECT and the alias. - env_key = TEST_CONFIG["gcloud_project_env"] - # This should error out if not set. - ret["GOOGLE_CLOUD_PROJECT"] = os.environ[env_key] - - # Apply user supplied envs. - ret.update(TEST_CONFIG["envs"]) - return ret - - -# DO NOT EDIT - automatically generated. -# All versions used to test samples. -ALL_VERSIONS = ["3.7", "3.8", "3.9", "3.10"] - -# Any default versions that should be ignored. -IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] - -TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS]) - -INSTALL_LIBRARY_FROM_SOURCE = os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False) in ( - "True", - "true", -) - -# Error if a python version is missing -nox.options.error_on_missing_interpreters = True - -# -# Style Checks -# - - -def _determine_local_import_names(start_dir: str) -> List[str]: - """Determines all import names that should be considered "local". - - This is used when running the linter to insure that import order is - properly checked. - """ - file_ext_pairs = [os.path.splitext(path) for path in os.listdir(start_dir)] - return [ - basename - for basename, extension in file_ext_pairs - if extension == ".py" - or os.path.isdir(os.path.join(start_dir, basename)) - and basename not in ("__pycache__") - ] - - -# Linting with flake8. -# -# We ignore the following rules: -# E203: whitespace before ‘:’ -# E266: too many leading ‘#’ for block comment -# E501: line too long -# I202: Additional newline in a section of imports -# -# We also need to specify the rules which are ignored by default: -# ['E226', 'W504', 'E126', 'E123', 'W503', 'E24', 'E704', 'E121'] -FLAKE8_COMMON_ARGS = [ - "--show-source", - "--builtin=gettext", - "--max-complexity=20", - "--import-order-style=google", - "--exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py", - "--ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202", - "--max-line-length=88", -] - - -@nox.session -def lint(session: nox.sessions.Session) -> None: - if not TEST_CONFIG["enforce_type_hints"]: - session.install("flake8", "flake8-import-order") - else: - session.install("flake8", "flake8-import-order", "flake8-annotations") - - local_names = _determine_local_import_names(".") - args = FLAKE8_COMMON_ARGS + [ - "--application-import-names", - ",".join(local_names), - ".", - ] - session.run("flake8", *args) - - -# -# Black -# - - -@nox.session -def blacken(session: nox.sessions.Session) -> None: - """Run black. Format code to uniform standard.""" - session.install(BLACK_VERSION) - python_files = [path for path in os.listdir(".") if path.endswith(".py")] - - session.run("black", *python_files) - - -# -# format = isort + black -# - - -@nox.session -def format(session: nox.sessions.Session) -> None: - """ - Run isort to sort imports. Then run black - to format code to uniform standard. - """ - session.install(BLACK_VERSION, ISORT_VERSION) - python_files = [path for path in os.listdir(".") if path.endswith(".py")] - - # Use the --fss option to sort imports using strict alphabetical order. - # See https://pycqa.github.io/isort/docs/configuration/options.html#force-sort-within-sections - session.run("isort", "--fss", *python_files) - session.run("black", *python_files) - - -# -# Sample Tests -# - - -PYTEST_COMMON_ARGS = ["--junitxml=sponge_log.xml"] - - -def _session_tests( - session: nox.sessions.Session, post_install: Callable = None -) -> None: - # check for presence of tests - test_list = glob.glob("**/*_test.py", recursive=True) + glob.glob( - "**/test_*.py", recursive=True - ) - test_list.extend(glob.glob("**/tests", recursive=True)) - - if len(test_list) == 0: - print("No tests found, skipping directory.") - return - - if TEST_CONFIG["pip_version_override"]: - pip_version = TEST_CONFIG["pip_version_override"] - session.install(f"pip=={pip_version}") - """Runs py.test for a particular project.""" - concurrent_args = [] - if os.path.exists("requirements.txt"): - if os.path.exists("constraints.txt"): - session.install("-r", "requirements.txt", "-c", "constraints.txt") - else: - session.install("-r", "requirements.txt") - with open("requirements.txt") as rfile: - packages = rfile.read() - - if os.path.exists("requirements-test.txt"): - if os.path.exists("constraints-test.txt"): - session.install("-r", "requirements-test.txt", "-c", "constraints-test.txt") - else: - session.install("-r", "requirements-test.txt") - with open("requirements-test.txt") as rtfile: - packages += rtfile.read() - - if INSTALL_LIBRARY_FROM_SOURCE: - session.install("-e", _get_repo_root()) - - if post_install: - post_install(session) - - if "pytest-parallel" in packages: - concurrent_args.extend(["--workers", "auto", "--tests-per-worker", "auto"]) - elif "pytest-xdist" in packages: - concurrent_args.extend(["-n", "auto"]) - - session.run( - "pytest", - *(PYTEST_COMMON_ARGS + session.posargs + concurrent_args), - # Pytest will return 5 when no tests are collected. This can happen - # on travis where slow and flaky tests are excluded. - # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html - success_codes=[0, 5], - env=get_pytest_env_vars(), - ) - - -@nox.session(python=ALL_VERSIONS) -def py(session: nox.sessions.Session) -> None: - """Runs py.test for a sample using the specified version of Python.""" - if session.python in TESTED_VERSIONS: - _session_tests(session) - else: - session.skip( - "SKIPPED: {} tests are disabled for this sample.".format(session.python) - ) - - -# -# Readmegen -# - - -def _get_repo_root() -> Optional[str]: - """Returns the root folder of the project.""" - # Get root of this repository. Assume we don't have directories nested deeper than 10 items. - p = Path(os.getcwd()) - for i in range(10): - if p is None: - break - if Path(p / ".git").exists(): - return str(p) - # .git is not available in repos cloned via Cloud Build - # setup.py is always in the library's root, so use that instead - # https://github.com/googleapis/synthtool/issues/792 - if Path(p / "setup.py").exists(): - return str(p) - p = p.parent - raise Exception("Unable to detect repository root.") - - -GENERATED_READMES = sorted([x for x in Path(".").rglob("*.rst.in")]) - - -@nox.session -@nox.parametrize("path", GENERATED_READMES) -def readmegen(session: nox.sessions.Session, path: str) -> None: - """(Re-)generates the readme for a sample.""" - session.install("jinja2", "pyyaml") - dir_ = os.path.dirname(path) - - if os.path.exists(os.path.join(dir_, "requirements.txt")): - session.install("-r", os.path.join(dir_, "requirements.txt")) - - in_file = os.path.join(dir_, "README.rst.in") - session.run( - "python", _get_repo_root() + "/scripts/readme-gen/readme_gen.py", in_file - ) From bb7571ba9af36f744b5b7fdb0e4db497b0202fe5 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 9 Nov 2022 16:03:24 +0100 Subject: [PATCH 110/113] Fixing license header --- .../instances/create_start_instance/create_from_custom_image.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.py b/compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.py index 1195b75872f..f3b9bd58ec7 100644 --- a/compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.py +++ b/compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.py @@ -1,3 +1,5 @@ +# Copyright 2022 Google LLC +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at From 93eb1a72864eacd36458964d606c82c79a72cef5 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 9 Nov 2022 16:59:40 +0100 Subject: [PATCH 111/113] Fixing a test --- compute/compute/snippets/tests/test_request_id.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compute/compute/snippets/tests/test_request_id.py b/compute/compute/snippets/tests/test_request_id.py index 6266dd2c21e..8cf25e87b1f 100644 --- a/compute/compute/snippets/tests/test_request_id.py +++ b/compute/compute/snippets/tests/test_request_id.py @@ -19,7 +19,7 @@ from google.cloud import compute_v1 import pytest -from samples.snippets.instances.create_start_instance.create_windows_instance import \ +from ..instances.create_start_instance.create_windows_instance import \ get_image_from_family from ..disks.delete import delete_disk From 74a8198543caa281d9e73a4fcda1c18f76cd5716 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Thu, 10 Nov 2022 14:01:34 +0100 Subject: [PATCH 112/113] Fixing lint --- compute/compute/snippets/tests/test_request_id.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compute/compute/snippets/tests/test_request_id.py b/compute/compute/snippets/tests/test_request_id.py index 8cf25e87b1f..90dcc7e028c 100644 --- a/compute/compute/snippets/tests/test_request_id.py +++ b/compute/compute/snippets/tests/test_request_id.py @@ -19,11 +19,11 @@ from google.cloud import compute_v1 import pytest -from ..instances.create_start_instance.create_windows_instance import \ - get_image_from_family 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" From a95ec4eb6cc8af95b44cf04cc4071980a5e2e9e2 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Tue, 15 Nov 2022 15:55:51 +0100 Subject: [PATCH 113/113] Renaming compute to client_library --- compute/{compute => client_library}/README.md | 0 compute/{compute => client_library}/__init__.py | 0 compute/{compute => client_library}/ingredients/__init__.py | 0 .../ingredients/disks/autodelete_change.py | 0 .../ingredients/disks/clone_encrypted_disk.py | 0 .../ingredients/disks/clone_encrypted_disk_managed_key.py | 0 .../ingredients/disks/create_empty_disk.py | 0 .../ingredients/disks/create_from_image.py | 0 .../ingredients/disks/create_from_snapshot.py | 0 .../ingredients/disks/create_from_source.py | 0 .../ingredients/disks/create_kms_encrypted_disk.py | 0 compute/{compute => client_library}/ingredients/disks/delete.py | 0 .../ingredients/disks/disk_from_snapshot.py | 0 .../{compute => client_library}/ingredients/disks/empty_disk.py | 0 .../{compute => client_library}/ingredients/disks/from_image.py | 0 compute/{compute => client_library}/ingredients/disks/get.py | 0 compute/{compute => client_library}/ingredients/disks/list.py | 0 .../{compute => client_library}/ingredients/disks/local_ssd.py | 0 .../ingredients/disks/regional_create_from_source.py | 0 .../ingredients/disks/regional_delete.py | 0 .../{compute => client_library}/ingredients/firewall/create.py | 0 .../{compute => client_library}/ingredients/firewall/delete.py | 0 compute/{compute => client_library}/ingredients/firewall/get.py | 0 compute/{compute => client_library}/ingredients/firewall/list.py | 0 compute/{compute => client_library}/ingredients/firewall/patch.py | 0 .../ingredients/firewall/windows_kms.py | 0 compute/{compute => client_library}/ingredients/images/create.py | 0 .../ingredients/images/create_from_image.py | 0 .../ingredients/images/create_from_snapshot.py | 0 compute/{compute => client_library}/ingredients/images/delete.py | 0 .../{compute => client_library}/ingredients/images/get_image.py | 0 .../ingredients/images/get_image_from_family.py | 0 .../{compute => client_library}/ingredients/images/list_images.py | 0 .../ingredients/images/set_depracation_status.py | 0 .../ingredients/instance-templates/create.py | 0 .../ingredients/instance-templates/create_from_instance.py | 0 .../ingredients/instance-templates/create_with_subnet.py | 0 .../ingredients/instance-templates/delete.py | 0 .../ingredients/instance-templates/get.py | 0 .../ingredients/instance-templates/list.py | 0 .../{compute => client_library}/ingredients/instances/__init__.py | 0 .../ingredients/instances/bulk_insert.py | 0 .../ingredients/instances/create_instance.py | 0 .../ingredients/instances/create_instance_from_template.py | 0 .../instances/create_instance_from_template_with_overrides.py | 0 .../instances/create_start_instance/create_from_custom_image.py | 0 .../instances/create_start_instance/create_from_public_image.py | 0 .../instances/create_start_instance/create_from_snapshot.py | 0 .../instances/create_start_instance/create_windows_instance.py | 0 .../create_start_instance/create_with_additional_disk.py | 0 .../instances/create_start_instance/create_with_existing_disks.py | 0 .../instances/create_start_instance/create_with_local_ssd.py | 0 .../create_start_instance/create_with_snapshotted_data_disk.py | 0 .../ingredients/instances/create_with_subnet.py | 0 .../ingredients/instances/custom_hostname/create.py | 0 .../ingredients/instances/custom_hostname/get.py | 0 .../instances/custom_machine_types/create_extra_mem_no_helper.py | 0 .../instances/custom_machine_types/create_shared_with_helper.py | 0 .../instances/custom_machine_types/create_with_helper.py | 0 .../instances/custom_machine_types/create_without_helper.py | 0 .../ingredients/instances/custom_machine_types/helper_class.py | 0 .../ingredients/instances/custom_machine_types/update_memory.py | 0 .../{compute => client_library}/ingredients/instances/delete.py | 0 .../ingredients/instances/delete_protection/__init__.py | 0 .../ingredients/instances/delete_protection/create.py | 0 .../ingredients/instances/delete_protection/get.py | 0 .../ingredients/instances/delete_protection/set.py | 0 compute/{compute => client_library}/ingredients/instances/get.py | 0 .../ingredients/instances/get_serial_port.py | 0 compute/{compute => client_library}/ingredients/instances/list.py | 0 .../{compute => client_library}/ingredients/instances/list_all.py | 0 .../ingredients/instances/preemptible/__init__.py | 0 .../ingredients/instances/preemptible/create.py | 0 .../ingredients/instances/preemptible/get.py | 0 .../ingredients/instances/preemptible/preemption_history.py | 0 .../{compute => client_library}/ingredients/instances/reset.py | 0 .../{compute => client_library}/ingredients/instances/resume.py | 0 .../ingredients/instances/spot/create.py | 0 .../{compute => client_library}/ingredients/instances/spot/get.py | 0 .../{compute => client_library}/ingredients/instances/start.py | 0 .../ingredients/instances/start_encrypted.py | 0 compute/{compute => client_library}/ingredients/instances/stop.py | 0 .../{compute => client_library}/ingredients/instances/suspend.py | 0 .../ingredients/operations/__init__.py | 0 .../ingredients/operations/handle_extended_operation.py | 0 .../ingredients/operations/list_zone_operations.py | 0 .../ingredients/operations/wait_for_operation.py | 0 compute/{compute => client_library}/ingredients/routes/create.py | 0 compute/{compute => client_library}/ingredients/routes/delete.py | 0 compute/{compute => client_library}/ingredients/routes/list.py | 0 .../{compute => client_library}/ingredients/snapshots/create.py | 0 .../{compute => client_library}/ingredients/snapshots/delete.py | 0 compute/{compute => client_library}/ingredients/snapshots/get.py | 0 compute/{compute => client_library}/ingredients/snapshots/list.py | 0 .../ingredients/usage_report/disable.py | 0 .../ingredients/usage_report/get_bucket.py | 0 .../ingredients/usage_report/set_bucket.py | 0 compute/{compute => client_library}/noxfile_config.py | 0 compute/{compute => client_library}/recipes/__init__.py | 0 compute/{compute => client_library}/recipes/disks/__init__.py | 0 .../recipes/disks/autodelete_change.py | 0 .../recipes/disks/clone_encrypted_disk.py | 0 .../recipes/disks/clone_encrypted_disk_managed_key.py | 0 .../recipes/disks/create_empty_disk.py | 0 .../recipes/disks/create_from_image.py | 0 .../recipes/disks/create_from_snapshot.py | 0 .../recipes/disks/create_from_source.py | 0 .../recipes/disks/create_kms_encrypted_disk.py | 0 compute/{compute => client_library}/recipes/disks/delete.py | 0 compute/{compute => client_library}/recipes/disks/list.py | 0 .../recipes/disks/regional_create_from_source.py | 0 .../{compute => client_library}/recipes/disks/regional_delete.py | 0 compute/{compute => client_library}/recipes/firewall/__init__.py | 0 compute/{compute => client_library}/recipes/firewall/create.py | 0 compute/{compute => client_library}/recipes/firewall/delete.py | 0 compute/{compute => client_library}/recipes/firewall/list.py | 0 compute/{compute => client_library}/recipes/firewall/main.py | 0 compute/{compute => client_library}/recipes/firewall/patch.py | 0 .../{compute => client_library}/recipes/firewall/windows_kms.py | 0 compute/{compute => client_library}/recipes/images/__init__.py | 0 compute/{compute => client_library}/recipes/images/create.py | 0 .../recipes/images/create_from_image.py | 0 .../recipes/images/create_from_snapshot.py | 0 compute/{compute => client_library}/recipes/images/delete.py | 0 compute/{compute => client_library}/recipes/images/get.py | 0 compute/{compute => client_library}/recipes/images/list.py | 0 compute/{compute => client_library}/recipes/images/pagination.py | 0 .../recipes/images/set_deprecation_status.py | 0 .../recipes/instance_templates/__init__.py | 0 .../recipes/instance_templates/create.py | 0 .../recipes/instance_templates/create_from_instance.py | 0 .../recipes/instance_templates/create_with_subnet.py | 0 .../recipes/instance_templates/delete.py | 0 .../{compute => client_library}/recipes/instance_templates/get.py | 0 .../recipes/instance_templates/list.py | 0 compute/{compute => client_library}/recipes/instances/__init__.py | 0 .../{compute => client_library}/recipes/instances/bulk_insert.py | 0 compute/{compute => client_library}/recipes/instances/create.py | 0 .../recipes/instances/create_start_instance/__init__.py | 0 .../instances/create_start_instance/create_from_custom_image.py | 0 .../instances/create_start_instance/create_from_public_image.py | 0 .../instances/create_start_instance/create_from_snapshot.py | 0 .../instances/create_start_instance/create_windows_instance.py | 0 .../create_start_instance/create_with_additional_disk.py | 0 .../instances/create_start_instance/create_with_existing_disks.py | 0 .../instances/create_start_instance/create_with_local_ssd.py | 0 .../create_start_instance/create_with_snapshotted_data_disk.py | 0 .../recipes/instances/create_with_subnet.py | 0 .../recipes/instances/custom_hostname/create.py | 0 .../recipes/instances/custom_hostname/get.py | 0 .../recipes/instances/custom_machine_types/__init__.py | 0 .../instances/custom_machine_types/create_shared_with_helper.py | 0 .../recipes/instances/custom_machine_types/create_with_helper.py | 0 .../instances/custom_machine_types/create_without_helper.py | 0 .../recipes/instances/custom_machine_types/extra_mem_no_helper.py | 0 .../recipes/instances/custom_machine_types/helper_class.py | 0 .../recipes/instances/custom_machine_types/update_memory.py | 0 compute/{compute => client_library}/recipes/instances/delete.py | 0 .../recipes/instances/delete_protection/__init__.py | 0 .../recipes/instances/delete_protection/create.py | 0 .../recipes/instances/delete_protection/get.py | 0 .../recipes/instances/delete_protection/set.py | 0 .../recipes/instances/from_instance_template/__init__.py | 0 .../instances/from_instance_template/create_from_template.py | 0 .../from_instance_template/create_from_template_with_overrides.py | 0 compute/{compute => client_library}/recipes/instances/get.py | 0 compute/{compute => client_library}/recipes/instances/list.py | 0 compute/{compute => client_library}/recipes/instances/list_all.py | 0 .../recipes/instances/preemptible/__init__.py | 0 .../recipes/instances/preemptible/create_preemptible.py | 0 .../recipes/instances/preemptible/is_preemptible.py | 0 .../recipes/instances/preemptible/preemption_history.py | 0 compute/{compute => client_library}/recipes/instances/reset.py | 0 compute/{compute => client_library}/recipes/instances/resume.py | 0 .../recipes/instances/spot/__init__.py | 0 .../{compute => client_library}/recipes/instances/spot/create.py | 0 .../recipes/instances/spot/is_spot_vm.py | 0 compute/{compute => client_library}/recipes/instances/start.py | 0 .../recipes/instances/start_encrypted.py | 0 compute/{compute => client_library}/recipes/instances/stop.py | 0 compute/{compute => client_library}/recipes/instances/suspend.py | 0 .../{compute => client_library}/recipes/operations/__init__.py | 0 .../recipes/operations/operation_check.py | 0 compute/{compute => client_library}/recipes/routes/create.py | 0 .../recipes/routes/create_kms_route.py | 0 compute/{compute => client_library}/recipes/routes/delete.py | 0 compute/{compute => client_library}/recipes/routes/list.py | 0 compute/{compute => client_library}/recipes/snapshots/__init__.py | 0 compute/{compute => client_library}/recipes/snapshots/create.py | 0 compute/{compute => client_library}/recipes/snapshots/delete.py | 0 .../recipes/snapshots/delete_by_filter.py | 0 compute/{compute => client_library}/recipes/snapshots/get.py | 0 compute/{compute => client_library}/recipes/snapshots/list.py | 0 .../{compute => client_library}/recipes/usage_report/__init__.py | 0 .../recipes/usage_report/usage_reports.py | 0 compute/{compute => client_library}/requirements-test.txt | 0 compute/{compute => client_library}/requirements.txt | 0 compute/{compute => client_library}/sgs.py | 0 .../sgs_test_fixtures/ingredients/ingredient1.pytest | 0 .../sgs_test_fixtures/ingredients/ingredient2.pytest | 0 .../sgs_test_fixtures/output/experimental_recipe.pytest | 0 .../sgs_test_fixtures/recipes/experimental_recipe.pytest | 0 compute/{compute => client_library}/snippets/README.md | 0 compute/{compute => client_library}/snippets/__init__.py | 0 compute/{compute => client_library}/snippets/disks/__init__.py | 0 .../snippets/disks/autodelete_change.py | 0 .../snippets/disks/clone_encrypted_disk.py | 0 .../snippets/disks/clone_encrypted_disk_managed_key.py | 0 .../snippets/disks/create_empty_disk.py | 0 .../snippets/disks/create_from_image.py | 0 .../snippets/disks/create_from_snapshot.py | 0 .../snippets/disks/create_from_source.py | 0 .../snippets/disks/create_kms_encrypted_disk.py | 0 compute/{compute => client_library}/snippets/disks/delete.py | 0 compute/{compute => client_library}/snippets/disks/list.py | 0 .../snippets/disks/regional_create_from_source.py | 0 .../{compute => client_library}/snippets/disks/regional_delete.py | 0 compute/{compute => client_library}/snippets/firewall/__init__.py | 0 compute/{compute => client_library}/snippets/firewall/create.py | 0 compute/{compute => client_library}/snippets/firewall/delete.py | 0 compute/{compute => client_library}/snippets/firewall/list.py | 0 compute/{compute => client_library}/snippets/firewall/main.py | 0 compute/{compute => client_library}/snippets/firewall/patch.py | 0 .../{compute => client_library}/snippets/firewall/windows_kms.py | 0 compute/{compute => client_library}/snippets/images/__init__.py | 0 compute/{compute => client_library}/snippets/images/create.py | 0 .../snippets/images/create_from_image.py | 0 .../snippets/images/create_from_snapshot.py | 0 compute/{compute => client_library}/snippets/images/delete.py | 0 compute/{compute => client_library}/snippets/images/get.py | 0 compute/{compute => client_library}/snippets/images/list.py | 0 compute/{compute => client_library}/snippets/images/pagination.py | 0 .../snippets/images/set_deprecation_status.py | 0 .../snippets/instance_templates/__init__.py | 0 .../snippets/instance_templates/create.py | 0 .../snippets/instance_templates/create_from_instance.py | 0 .../snippets/instance_templates/create_with_subnet.py | 0 .../snippets/instance_templates/delete.py | 0 .../snippets/instance_templates/get.py | 0 .../snippets/instance_templates/list.py | 0 .../{compute => client_library}/snippets/instances/__init__.py | 0 .../{compute => client_library}/snippets/instances/bulk_insert.py | 0 compute/{compute => client_library}/snippets/instances/create.py | 0 .../snippets/instances/create_start_instance/__init__.py | 0 .../instances/create_start_instance/create_from_custom_image.py | 0 .../instances/create_start_instance/create_from_public_image.py | 0 .../instances/create_start_instance/create_from_snapshot.py | 0 .../instances/create_start_instance/create_windows_instance.py | 0 .../create_start_instance/create_with_additional_disk.py | 0 .../instances/create_start_instance/create_with_existing_disks.py | 0 .../instances/create_start_instance/create_with_local_ssd.py | 0 .../create_start_instance/create_with_snapshotted_data_disk.py | 0 .../snippets/instances/create_with_subnet.py | 0 .../snippets/instances/custom_hostname/create.py | 0 .../snippets/instances/custom_hostname/get.py | 0 .../snippets/instances/custom_machine_types/__init__.py | 0 .../instances/custom_machine_types/create_shared_with_helper.py | 0 .../snippets/instances/custom_machine_types/create_with_helper.py | 0 .../instances/custom_machine_types/create_without_helper.py | 0 .../instances/custom_machine_types/extra_mem_no_helper.py | 0 .../snippets/instances/custom_machine_types/helper_class.py | 0 .../snippets/instances/custom_machine_types/update_memory.py | 0 compute/{compute => client_library}/snippets/instances/delete.py | 0 .../snippets/instances/delete_protection/__init__.py | 0 .../snippets/instances/delete_protection/create.py | 0 .../snippets/instances/delete_protection/get.py | 0 .../snippets/instances/delete_protection/set.py | 0 .../snippets/instances/from_instance_template/__init__.py | 0 .../instances/from_instance_template/create_from_template.py | 0 .../from_instance_template/create_from_template_with_overrides.py | 0 compute/{compute => client_library}/snippets/instances/get.py | 0 compute/{compute => client_library}/snippets/instances/list.py | 0 .../{compute => client_library}/snippets/instances/list_all.py | 0 .../snippets/instances/preemptible/__init__.py | 0 .../snippets/instances/preemptible/create_preemptible.py | 0 .../snippets/instances/preemptible/is_preemptible.py | 0 .../snippets/instances/preemptible/preemption_history.py | 0 compute/{compute => client_library}/snippets/instances/reset.py | 0 compute/{compute => client_library}/snippets/instances/resume.py | 0 .../snippets/instances/spot/__init__.py | 0 .../{compute => client_library}/snippets/instances/spot/create.py | 0 .../snippets/instances/spot/is_spot_vm.py | 0 compute/{compute => client_library}/snippets/instances/start.py | 0 .../snippets/instances/start_encrypted.py | 0 compute/{compute => client_library}/snippets/instances/stop.py | 0 compute/{compute => client_library}/snippets/instances/suspend.py | 0 .../{compute => client_library}/snippets/operations/__init__.py | 0 .../snippets/operations/operation_check.py | 0 compute/{compute => client_library}/snippets/routes/create.py | 0 .../snippets/routes/create_kms_route.py | 0 compute/{compute => client_library}/snippets/routes/delete.py | 0 compute/{compute => client_library}/snippets/routes/list.py | 0 .../{compute => client_library}/snippets/snapshots/__init__.py | 0 compute/{compute => client_library}/snippets/snapshots/create.py | 0 compute/{compute => client_library}/snippets/snapshots/delete.py | 0 .../snippets/snapshots/delete_by_filter.py | 0 compute/{compute => client_library}/snippets/snapshots/get.py | 0 compute/{compute => client_library}/snippets/snapshots/list.py | 0 compute/{compute => client_library}/snippets/tests/__init__.py | 0 compute/{compute => client_library}/snippets/tests/test_bulk.py | 0 .../{compute => client_library}/snippets/tests/test_create_vm.py | 0 .../snippets/tests/test_custom_hostnames.py | 0 .../snippets/tests/test_custom_types.py | 0 .../snippets/tests/test_default_values.py | 0 .../snippets/tests/test_delete_protection.py | 0 compute/{compute => client_library}/snippets/tests/test_disks.py | 0 .../{compute => client_library}/snippets/tests/test_firewall.py | 0 compute/{compute => client_library}/snippets/tests/test_images.py | 0 .../snippets/tests/test_instance_from_template.py | 0 .../snippets/tests/test_instance_start_stop.py | 0 .../snippets/tests/test_instance_suspend_resume.py | 0 .../{compute => client_library}/snippets/tests/test_pagination.py | 0 .../snippets/tests/test_preemptible.py | 0 .../{compute => client_library}/snippets/tests/test_request_id.py | 0 compute/{compute => client_library}/snippets/tests/test_route.py | 0 .../{compute => client_library}/snippets/tests/test_snapshots.py | 0 .../{compute => client_library}/snippets/tests/test_spot_vms.py | 0 .../{compute => client_library}/snippets/tests/test_templates.py | 0 .../{compute => client_library}/snippets/usage_report/__init__.py | 0 .../snippets/usage_report/usage_reports.py | 0 compute/{compute => client_library}/test_sgs.py | 0 321 files changed, 0 insertions(+), 0 deletions(-) rename compute/{compute => client_library}/README.md (100%) rename compute/{compute => client_library}/__init__.py (100%) rename compute/{compute => client_library}/ingredients/__init__.py (100%) rename compute/{compute => client_library}/ingredients/disks/autodelete_change.py (100%) rename compute/{compute => client_library}/ingredients/disks/clone_encrypted_disk.py (100%) rename compute/{compute => client_library}/ingredients/disks/clone_encrypted_disk_managed_key.py (100%) rename compute/{compute => client_library}/ingredients/disks/create_empty_disk.py (100%) rename compute/{compute => client_library}/ingredients/disks/create_from_image.py (100%) rename compute/{compute => client_library}/ingredients/disks/create_from_snapshot.py (100%) rename compute/{compute => client_library}/ingredients/disks/create_from_source.py (100%) rename compute/{compute => client_library}/ingredients/disks/create_kms_encrypted_disk.py (100%) rename compute/{compute => client_library}/ingredients/disks/delete.py (100%) rename compute/{compute => client_library}/ingredients/disks/disk_from_snapshot.py (100%) rename compute/{compute => client_library}/ingredients/disks/empty_disk.py (100%) rename compute/{compute => client_library}/ingredients/disks/from_image.py (100%) rename compute/{compute => client_library}/ingredients/disks/get.py (100%) rename compute/{compute => client_library}/ingredients/disks/list.py (100%) rename compute/{compute => client_library}/ingredients/disks/local_ssd.py (100%) rename compute/{compute => client_library}/ingredients/disks/regional_create_from_source.py (100%) rename compute/{compute => client_library}/ingredients/disks/regional_delete.py (100%) rename compute/{compute => client_library}/ingredients/firewall/create.py (100%) rename compute/{compute => client_library}/ingredients/firewall/delete.py (100%) rename compute/{compute => client_library}/ingredients/firewall/get.py (100%) rename compute/{compute => client_library}/ingredients/firewall/list.py (100%) rename compute/{compute => client_library}/ingredients/firewall/patch.py (100%) rename compute/{compute => client_library}/ingredients/firewall/windows_kms.py (100%) rename compute/{compute => client_library}/ingredients/images/create.py (100%) rename compute/{compute => client_library}/ingredients/images/create_from_image.py (100%) rename compute/{compute => client_library}/ingredients/images/create_from_snapshot.py (100%) rename compute/{compute => client_library}/ingredients/images/delete.py (100%) rename compute/{compute => client_library}/ingredients/images/get_image.py (100%) rename compute/{compute => client_library}/ingredients/images/get_image_from_family.py (100%) rename compute/{compute => client_library}/ingredients/images/list_images.py (100%) rename compute/{compute => client_library}/ingredients/images/set_depracation_status.py (100%) rename compute/{compute => client_library}/ingredients/instance-templates/create.py (100%) rename compute/{compute => client_library}/ingredients/instance-templates/create_from_instance.py (100%) rename compute/{compute => client_library}/ingredients/instance-templates/create_with_subnet.py (100%) rename compute/{compute => client_library}/ingredients/instance-templates/delete.py (100%) rename compute/{compute => client_library}/ingredients/instance-templates/get.py (100%) rename compute/{compute => client_library}/ingredients/instance-templates/list.py (100%) rename compute/{compute => client_library}/ingredients/instances/__init__.py (100%) rename compute/{compute => client_library}/ingredients/instances/bulk_insert.py (100%) rename compute/{compute => client_library}/ingredients/instances/create_instance.py (100%) rename compute/{compute => client_library}/ingredients/instances/create_instance_from_template.py (100%) rename compute/{compute => client_library}/ingredients/instances/create_instance_from_template_with_overrides.py (100%) rename compute/{compute => client_library}/ingredients/instances/create_start_instance/create_from_custom_image.py (100%) rename compute/{compute => client_library}/ingredients/instances/create_start_instance/create_from_public_image.py (100%) rename compute/{compute => client_library}/ingredients/instances/create_start_instance/create_from_snapshot.py (100%) rename compute/{compute => client_library}/ingredients/instances/create_start_instance/create_windows_instance.py (100%) rename compute/{compute => client_library}/ingredients/instances/create_start_instance/create_with_additional_disk.py (100%) rename compute/{compute => client_library}/ingredients/instances/create_start_instance/create_with_existing_disks.py (100%) rename compute/{compute => client_library}/ingredients/instances/create_start_instance/create_with_local_ssd.py (100%) rename compute/{compute => client_library}/ingredients/instances/create_start_instance/create_with_snapshotted_data_disk.py (100%) rename compute/{compute => client_library}/ingredients/instances/create_with_subnet.py (100%) rename compute/{compute => client_library}/ingredients/instances/custom_hostname/create.py (100%) rename compute/{compute => client_library}/ingredients/instances/custom_hostname/get.py (100%) rename compute/{compute => client_library}/ingredients/instances/custom_machine_types/create_extra_mem_no_helper.py (100%) rename compute/{compute => client_library}/ingredients/instances/custom_machine_types/create_shared_with_helper.py (100%) rename compute/{compute => client_library}/ingredients/instances/custom_machine_types/create_with_helper.py (100%) rename compute/{compute => client_library}/ingredients/instances/custom_machine_types/create_without_helper.py (100%) rename compute/{compute => client_library}/ingredients/instances/custom_machine_types/helper_class.py (100%) rename compute/{compute => client_library}/ingredients/instances/custom_machine_types/update_memory.py (100%) rename compute/{compute => client_library}/ingredients/instances/delete.py (100%) rename compute/{compute => client_library}/ingredients/instances/delete_protection/__init__.py (100%) rename compute/{compute => client_library}/ingredients/instances/delete_protection/create.py (100%) rename compute/{compute => client_library}/ingredients/instances/delete_protection/get.py (100%) rename compute/{compute => client_library}/ingredients/instances/delete_protection/set.py (100%) rename compute/{compute => client_library}/ingredients/instances/get.py (100%) rename compute/{compute => client_library}/ingredients/instances/get_serial_port.py (100%) rename compute/{compute => client_library}/ingredients/instances/list.py (100%) rename compute/{compute => client_library}/ingredients/instances/list_all.py (100%) rename compute/{compute => client_library}/ingredients/instances/preemptible/__init__.py (100%) rename compute/{compute => client_library}/ingredients/instances/preemptible/create.py (100%) rename compute/{compute => client_library}/ingredients/instances/preemptible/get.py (100%) rename compute/{compute => client_library}/ingredients/instances/preemptible/preemption_history.py (100%) rename compute/{compute => client_library}/ingredients/instances/reset.py (100%) rename compute/{compute => client_library}/ingredients/instances/resume.py (100%) rename compute/{compute => client_library}/ingredients/instances/spot/create.py (100%) rename compute/{compute => client_library}/ingredients/instances/spot/get.py (100%) rename compute/{compute => client_library}/ingredients/instances/start.py (100%) rename compute/{compute => client_library}/ingredients/instances/start_encrypted.py (100%) rename compute/{compute => client_library}/ingredients/instances/stop.py (100%) rename compute/{compute => client_library}/ingredients/instances/suspend.py (100%) rename compute/{compute => client_library}/ingredients/operations/__init__.py (100%) rename compute/{compute => client_library}/ingredients/operations/handle_extended_operation.py (100%) rename compute/{compute => client_library}/ingredients/operations/list_zone_operations.py (100%) rename compute/{compute => client_library}/ingredients/operations/wait_for_operation.py (100%) rename compute/{compute => client_library}/ingredients/routes/create.py (100%) rename compute/{compute => client_library}/ingredients/routes/delete.py (100%) rename compute/{compute => client_library}/ingredients/routes/list.py (100%) rename compute/{compute => client_library}/ingredients/snapshots/create.py (100%) rename compute/{compute => client_library}/ingredients/snapshots/delete.py (100%) rename compute/{compute => client_library}/ingredients/snapshots/get.py (100%) rename compute/{compute => client_library}/ingredients/snapshots/list.py (100%) rename compute/{compute => client_library}/ingredients/usage_report/disable.py (100%) rename compute/{compute => client_library}/ingredients/usage_report/get_bucket.py (100%) rename compute/{compute => client_library}/ingredients/usage_report/set_bucket.py (100%) rename compute/{compute => client_library}/noxfile_config.py (100%) rename compute/{compute => client_library}/recipes/__init__.py (100%) rename compute/{compute => client_library}/recipes/disks/__init__.py (100%) rename compute/{compute => client_library}/recipes/disks/autodelete_change.py (100%) rename compute/{compute => client_library}/recipes/disks/clone_encrypted_disk.py (100%) rename compute/{compute => client_library}/recipes/disks/clone_encrypted_disk_managed_key.py (100%) rename compute/{compute => client_library}/recipes/disks/create_empty_disk.py (100%) rename compute/{compute => client_library}/recipes/disks/create_from_image.py (100%) rename compute/{compute => client_library}/recipes/disks/create_from_snapshot.py (100%) rename compute/{compute => client_library}/recipes/disks/create_from_source.py (100%) rename compute/{compute => client_library}/recipes/disks/create_kms_encrypted_disk.py (100%) rename compute/{compute => client_library}/recipes/disks/delete.py (100%) rename compute/{compute => client_library}/recipes/disks/list.py (100%) rename compute/{compute => client_library}/recipes/disks/regional_create_from_source.py (100%) rename compute/{compute => client_library}/recipes/disks/regional_delete.py (100%) rename compute/{compute => client_library}/recipes/firewall/__init__.py (100%) rename compute/{compute => client_library}/recipes/firewall/create.py (100%) rename compute/{compute => client_library}/recipes/firewall/delete.py (100%) rename compute/{compute => client_library}/recipes/firewall/list.py (100%) rename compute/{compute => client_library}/recipes/firewall/main.py (100%) rename compute/{compute => client_library}/recipes/firewall/patch.py (100%) rename compute/{compute => client_library}/recipes/firewall/windows_kms.py (100%) rename compute/{compute => client_library}/recipes/images/__init__.py (100%) rename compute/{compute => client_library}/recipes/images/create.py (100%) rename compute/{compute => client_library}/recipes/images/create_from_image.py (100%) rename compute/{compute => client_library}/recipes/images/create_from_snapshot.py (100%) rename compute/{compute => client_library}/recipes/images/delete.py (100%) rename compute/{compute => client_library}/recipes/images/get.py (100%) rename compute/{compute => client_library}/recipes/images/list.py (100%) rename compute/{compute => client_library}/recipes/images/pagination.py (100%) rename compute/{compute => client_library}/recipes/images/set_deprecation_status.py (100%) rename compute/{compute => client_library}/recipes/instance_templates/__init__.py (100%) rename compute/{compute => client_library}/recipes/instance_templates/create.py (100%) rename compute/{compute => client_library}/recipes/instance_templates/create_from_instance.py (100%) rename compute/{compute => client_library}/recipes/instance_templates/create_with_subnet.py (100%) rename compute/{compute => client_library}/recipes/instance_templates/delete.py (100%) rename compute/{compute => client_library}/recipes/instance_templates/get.py (100%) rename compute/{compute => client_library}/recipes/instance_templates/list.py (100%) rename compute/{compute => client_library}/recipes/instances/__init__.py (100%) rename compute/{compute => client_library}/recipes/instances/bulk_insert.py (100%) rename compute/{compute => client_library}/recipes/instances/create.py (100%) rename compute/{compute => client_library}/recipes/instances/create_start_instance/__init__.py (100%) rename compute/{compute => client_library}/recipes/instances/create_start_instance/create_from_custom_image.py (100%) rename compute/{compute => client_library}/recipes/instances/create_start_instance/create_from_public_image.py (100%) rename compute/{compute => client_library}/recipes/instances/create_start_instance/create_from_snapshot.py (100%) rename compute/{compute => client_library}/recipes/instances/create_start_instance/create_windows_instance.py (100%) rename compute/{compute => client_library}/recipes/instances/create_start_instance/create_with_additional_disk.py (100%) rename compute/{compute => client_library}/recipes/instances/create_start_instance/create_with_existing_disks.py (100%) rename compute/{compute => client_library}/recipes/instances/create_start_instance/create_with_local_ssd.py (100%) rename compute/{compute => client_library}/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.py (100%) rename compute/{compute => client_library}/recipes/instances/create_with_subnet.py (100%) rename compute/{compute => client_library}/recipes/instances/custom_hostname/create.py (100%) rename compute/{compute => client_library}/recipes/instances/custom_hostname/get.py (100%) rename compute/{compute => client_library}/recipes/instances/custom_machine_types/__init__.py (100%) rename compute/{compute => client_library}/recipes/instances/custom_machine_types/create_shared_with_helper.py (100%) rename compute/{compute => client_library}/recipes/instances/custom_machine_types/create_with_helper.py (100%) rename compute/{compute => client_library}/recipes/instances/custom_machine_types/create_without_helper.py (100%) rename compute/{compute => client_library}/recipes/instances/custom_machine_types/extra_mem_no_helper.py (100%) rename compute/{compute => client_library}/recipes/instances/custom_machine_types/helper_class.py (100%) rename compute/{compute => client_library}/recipes/instances/custom_machine_types/update_memory.py (100%) rename compute/{compute => client_library}/recipes/instances/delete.py (100%) rename compute/{compute => client_library}/recipes/instances/delete_protection/__init__.py (100%) rename compute/{compute => client_library}/recipes/instances/delete_protection/create.py (100%) rename compute/{compute => client_library}/recipes/instances/delete_protection/get.py (100%) rename compute/{compute => client_library}/recipes/instances/delete_protection/set.py (100%) rename compute/{compute => client_library}/recipes/instances/from_instance_template/__init__.py (100%) rename compute/{compute => client_library}/recipes/instances/from_instance_template/create_from_template.py (100%) rename compute/{compute => client_library}/recipes/instances/from_instance_template/create_from_template_with_overrides.py (100%) rename compute/{compute => client_library}/recipes/instances/get.py (100%) rename compute/{compute => client_library}/recipes/instances/list.py (100%) rename compute/{compute => client_library}/recipes/instances/list_all.py (100%) rename compute/{compute => client_library}/recipes/instances/preemptible/__init__.py (100%) rename compute/{compute => client_library}/recipes/instances/preemptible/create_preemptible.py (100%) rename compute/{compute => client_library}/recipes/instances/preemptible/is_preemptible.py (100%) rename compute/{compute => client_library}/recipes/instances/preemptible/preemption_history.py (100%) rename compute/{compute => client_library}/recipes/instances/reset.py (100%) rename compute/{compute => client_library}/recipes/instances/resume.py (100%) rename compute/{compute => client_library}/recipes/instances/spot/__init__.py (100%) rename compute/{compute => client_library}/recipes/instances/spot/create.py (100%) rename compute/{compute => client_library}/recipes/instances/spot/is_spot_vm.py (100%) rename compute/{compute => client_library}/recipes/instances/start.py (100%) rename compute/{compute => client_library}/recipes/instances/start_encrypted.py (100%) rename compute/{compute => client_library}/recipes/instances/stop.py (100%) rename compute/{compute => client_library}/recipes/instances/suspend.py (100%) rename compute/{compute => client_library}/recipes/operations/__init__.py (100%) rename compute/{compute => client_library}/recipes/operations/operation_check.py (100%) rename compute/{compute => client_library}/recipes/routes/create.py (100%) rename compute/{compute => client_library}/recipes/routes/create_kms_route.py (100%) rename compute/{compute => client_library}/recipes/routes/delete.py (100%) rename compute/{compute => client_library}/recipes/routes/list.py (100%) rename compute/{compute => client_library}/recipes/snapshots/__init__.py (100%) rename compute/{compute => client_library}/recipes/snapshots/create.py (100%) rename compute/{compute => client_library}/recipes/snapshots/delete.py (100%) rename compute/{compute => client_library}/recipes/snapshots/delete_by_filter.py (100%) rename compute/{compute => client_library}/recipes/snapshots/get.py (100%) rename compute/{compute => client_library}/recipes/snapshots/list.py (100%) rename compute/{compute => client_library}/recipes/usage_report/__init__.py (100%) rename compute/{compute => client_library}/recipes/usage_report/usage_reports.py (100%) rename compute/{compute => client_library}/requirements-test.txt (100%) rename compute/{compute => client_library}/requirements.txt (100%) rename compute/{compute => client_library}/sgs.py (100%) rename compute/{compute => client_library}/sgs_test_fixtures/ingredients/ingredient1.pytest (100%) rename compute/{compute => client_library}/sgs_test_fixtures/ingredients/ingredient2.pytest (100%) rename compute/{compute => client_library}/sgs_test_fixtures/output/experimental_recipe.pytest (100%) rename compute/{compute => client_library}/sgs_test_fixtures/recipes/experimental_recipe.pytest (100%) rename compute/{compute => client_library}/snippets/README.md (100%) rename compute/{compute => client_library}/snippets/__init__.py (100%) rename compute/{compute => client_library}/snippets/disks/__init__.py (100%) rename compute/{compute => client_library}/snippets/disks/autodelete_change.py (100%) rename compute/{compute => client_library}/snippets/disks/clone_encrypted_disk.py (100%) rename compute/{compute => client_library}/snippets/disks/clone_encrypted_disk_managed_key.py (100%) rename compute/{compute => client_library}/snippets/disks/create_empty_disk.py (100%) rename compute/{compute => client_library}/snippets/disks/create_from_image.py (100%) rename compute/{compute => client_library}/snippets/disks/create_from_snapshot.py (100%) rename compute/{compute => client_library}/snippets/disks/create_from_source.py (100%) rename compute/{compute => client_library}/snippets/disks/create_kms_encrypted_disk.py (100%) rename compute/{compute => client_library}/snippets/disks/delete.py (100%) rename compute/{compute => client_library}/snippets/disks/list.py (100%) rename compute/{compute => client_library}/snippets/disks/regional_create_from_source.py (100%) rename compute/{compute => client_library}/snippets/disks/regional_delete.py (100%) rename compute/{compute => client_library}/snippets/firewall/__init__.py (100%) rename compute/{compute => client_library}/snippets/firewall/create.py (100%) rename compute/{compute => client_library}/snippets/firewall/delete.py (100%) rename compute/{compute => client_library}/snippets/firewall/list.py (100%) rename compute/{compute => client_library}/snippets/firewall/main.py (100%) rename compute/{compute => client_library}/snippets/firewall/patch.py (100%) rename compute/{compute => client_library}/snippets/firewall/windows_kms.py (100%) rename compute/{compute => client_library}/snippets/images/__init__.py (100%) rename compute/{compute => client_library}/snippets/images/create.py (100%) rename compute/{compute => client_library}/snippets/images/create_from_image.py (100%) rename compute/{compute => client_library}/snippets/images/create_from_snapshot.py (100%) rename compute/{compute => client_library}/snippets/images/delete.py (100%) rename compute/{compute => client_library}/snippets/images/get.py (100%) rename compute/{compute => client_library}/snippets/images/list.py (100%) rename compute/{compute => client_library}/snippets/images/pagination.py (100%) rename compute/{compute => client_library}/snippets/images/set_deprecation_status.py (100%) rename compute/{compute => client_library}/snippets/instance_templates/__init__.py (100%) rename compute/{compute => client_library}/snippets/instance_templates/create.py (100%) rename compute/{compute => client_library}/snippets/instance_templates/create_from_instance.py (100%) rename compute/{compute => client_library}/snippets/instance_templates/create_with_subnet.py (100%) rename compute/{compute => client_library}/snippets/instance_templates/delete.py (100%) rename compute/{compute => client_library}/snippets/instance_templates/get.py (100%) rename compute/{compute => client_library}/snippets/instance_templates/list.py (100%) rename compute/{compute => client_library}/snippets/instances/__init__.py (100%) rename compute/{compute => client_library}/snippets/instances/bulk_insert.py (100%) rename compute/{compute => client_library}/snippets/instances/create.py (100%) rename compute/{compute => client_library}/snippets/instances/create_start_instance/__init__.py (100%) rename compute/{compute => client_library}/snippets/instances/create_start_instance/create_from_custom_image.py (100%) rename compute/{compute => client_library}/snippets/instances/create_start_instance/create_from_public_image.py (100%) rename compute/{compute => client_library}/snippets/instances/create_start_instance/create_from_snapshot.py (100%) rename compute/{compute => client_library}/snippets/instances/create_start_instance/create_windows_instance.py (100%) rename compute/{compute => client_library}/snippets/instances/create_start_instance/create_with_additional_disk.py (100%) rename compute/{compute => client_library}/snippets/instances/create_start_instance/create_with_existing_disks.py (100%) rename compute/{compute => client_library}/snippets/instances/create_start_instance/create_with_local_ssd.py (100%) rename compute/{compute => client_library}/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py (100%) rename compute/{compute => client_library}/snippets/instances/create_with_subnet.py (100%) rename compute/{compute => client_library}/snippets/instances/custom_hostname/create.py (100%) rename compute/{compute => client_library}/snippets/instances/custom_hostname/get.py (100%) rename compute/{compute => client_library}/snippets/instances/custom_machine_types/__init__.py (100%) rename compute/{compute => client_library}/snippets/instances/custom_machine_types/create_shared_with_helper.py (100%) rename compute/{compute => client_library}/snippets/instances/custom_machine_types/create_with_helper.py (100%) rename compute/{compute => client_library}/snippets/instances/custom_machine_types/create_without_helper.py (100%) rename compute/{compute => client_library}/snippets/instances/custom_machine_types/extra_mem_no_helper.py (100%) rename compute/{compute => client_library}/snippets/instances/custom_machine_types/helper_class.py (100%) rename compute/{compute => client_library}/snippets/instances/custom_machine_types/update_memory.py (100%) rename compute/{compute => client_library}/snippets/instances/delete.py (100%) rename compute/{compute => client_library}/snippets/instances/delete_protection/__init__.py (100%) rename compute/{compute => client_library}/snippets/instances/delete_protection/create.py (100%) rename compute/{compute => client_library}/snippets/instances/delete_protection/get.py (100%) rename compute/{compute => client_library}/snippets/instances/delete_protection/set.py (100%) rename compute/{compute => client_library}/snippets/instances/from_instance_template/__init__.py (100%) rename compute/{compute => client_library}/snippets/instances/from_instance_template/create_from_template.py (100%) rename compute/{compute => client_library}/snippets/instances/from_instance_template/create_from_template_with_overrides.py (100%) rename compute/{compute => client_library}/snippets/instances/get.py (100%) rename compute/{compute => client_library}/snippets/instances/list.py (100%) rename compute/{compute => client_library}/snippets/instances/list_all.py (100%) rename compute/{compute => client_library}/snippets/instances/preemptible/__init__.py (100%) rename compute/{compute => client_library}/snippets/instances/preemptible/create_preemptible.py (100%) rename compute/{compute => client_library}/snippets/instances/preemptible/is_preemptible.py (100%) rename compute/{compute => client_library}/snippets/instances/preemptible/preemption_history.py (100%) rename compute/{compute => client_library}/snippets/instances/reset.py (100%) rename compute/{compute => client_library}/snippets/instances/resume.py (100%) rename compute/{compute => client_library}/snippets/instances/spot/__init__.py (100%) rename compute/{compute => client_library}/snippets/instances/spot/create.py (100%) rename compute/{compute => client_library}/snippets/instances/spot/is_spot_vm.py (100%) rename compute/{compute => client_library}/snippets/instances/start.py (100%) rename compute/{compute => client_library}/snippets/instances/start_encrypted.py (100%) rename compute/{compute => client_library}/snippets/instances/stop.py (100%) rename compute/{compute => client_library}/snippets/instances/suspend.py (100%) rename compute/{compute => client_library}/snippets/operations/__init__.py (100%) rename compute/{compute => client_library}/snippets/operations/operation_check.py (100%) rename compute/{compute => client_library}/snippets/routes/create.py (100%) rename compute/{compute => client_library}/snippets/routes/create_kms_route.py (100%) rename compute/{compute => client_library}/snippets/routes/delete.py (100%) rename compute/{compute => client_library}/snippets/routes/list.py (100%) rename compute/{compute => client_library}/snippets/snapshots/__init__.py (100%) rename compute/{compute => client_library}/snippets/snapshots/create.py (100%) rename compute/{compute => client_library}/snippets/snapshots/delete.py (100%) rename compute/{compute => client_library}/snippets/snapshots/delete_by_filter.py (100%) rename compute/{compute => client_library}/snippets/snapshots/get.py (100%) rename compute/{compute => client_library}/snippets/snapshots/list.py (100%) rename compute/{compute => client_library}/snippets/tests/__init__.py (100%) rename compute/{compute => client_library}/snippets/tests/test_bulk.py (100%) rename compute/{compute => client_library}/snippets/tests/test_create_vm.py (100%) rename compute/{compute => client_library}/snippets/tests/test_custom_hostnames.py (100%) rename compute/{compute => client_library}/snippets/tests/test_custom_types.py (100%) rename compute/{compute => client_library}/snippets/tests/test_default_values.py (100%) rename compute/{compute => client_library}/snippets/tests/test_delete_protection.py (100%) rename compute/{compute => client_library}/snippets/tests/test_disks.py (100%) rename compute/{compute => client_library}/snippets/tests/test_firewall.py (100%) rename compute/{compute => client_library}/snippets/tests/test_images.py (100%) rename compute/{compute => client_library}/snippets/tests/test_instance_from_template.py (100%) rename compute/{compute => client_library}/snippets/tests/test_instance_start_stop.py (100%) rename compute/{compute => client_library}/snippets/tests/test_instance_suspend_resume.py (100%) rename compute/{compute => client_library}/snippets/tests/test_pagination.py (100%) rename compute/{compute => client_library}/snippets/tests/test_preemptible.py (100%) rename compute/{compute => client_library}/snippets/tests/test_request_id.py (100%) rename compute/{compute => client_library}/snippets/tests/test_route.py (100%) rename compute/{compute => client_library}/snippets/tests/test_snapshots.py (100%) rename compute/{compute => client_library}/snippets/tests/test_spot_vms.py (100%) rename compute/{compute => client_library}/snippets/tests/test_templates.py (100%) rename compute/{compute => client_library}/snippets/usage_report/__init__.py (100%) rename compute/{compute => client_library}/snippets/usage_report/usage_reports.py (100%) rename compute/{compute => client_library}/test_sgs.py (100%) diff --git a/compute/compute/README.md b/compute/client_library/README.md similarity index 100% rename from compute/compute/README.md rename to compute/client_library/README.md diff --git a/compute/compute/__init__.py b/compute/client_library/__init__.py similarity index 100% rename from compute/compute/__init__.py rename to compute/client_library/__init__.py diff --git a/compute/compute/ingredients/__init__.py b/compute/client_library/ingredients/__init__.py similarity index 100% rename from compute/compute/ingredients/__init__.py rename to compute/client_library/ingredients/__init__.py diff --git a/compute/compute/ingredients/disks/autodelete_change.py b/compute/client_library/ingredients/disks/autodelete_change.py similarity index 100% rename from compute/compute/ingredients/disks/autodelete_change.py rename to compute/client_library/ingredients/disks/autodelete_change.py diff --git a/compute/compute/ingredients/disks/clone_encrypted_disk.py b/compute/client_library/ingredients/disks/clone_encrypted_disk.py similarity index 100% rename from compute/compute/ingredients/disks/clone_encrypted_disk.py rename to compute/client_library/ingredients/disks/clone_encrypted_disk.py diff --git a/compute/compute/ingredients/disks/clone_encrypted_disk_managed_key.py b/compute/client_library/ingredients/disks/clone_encrypted_disk_managed_key.py similarity index 100% rename from compute/compute/ingredients/disks/clone_encrypted_disk_managed_key.py rename to compute/client_library/ingredients/disks/clone_encrypted_disk_managed_key.py diff --git a/compute/compute/ingredients/disks/create_empty_disk.py b/compute/client_library/ingredients/disks/create_empty_disk.py similarity index 100% rename from compute/compute/ingredients/disks/create_empty_disk.py rename to compute/client_library/ingredients/disks/create_empty_disk.py diff --git a/compute/compute/ingredients/disks/create_from_image.py b/compute/client_library/ingredients/disks/create_from_image.py similarity index 100% rename from compute/compute/ingredients/disks/create_from_image.py rename to compute/client_library/ingredients/disks/create_from_image.py diff --git a/compute/compute/ingredients/disks/create_from_snapshot.py b/compute/client_library/ingredients/disks/create_from_snapshot.py similarity index 100% rename from compute/compute/ingredients/disks/create_from_snapshot.py rename to compute/client_library/ingredients/disks/create_from_snapshot.py diff --git a/compute/compute/ingredients/disks/create_from_source.py b/compute/client_library/ingredients/disks/create_from_source.py similarity index 100% rename from compute/compute/ingredients/disks/create_from_source.py rename to compute/client_library/ingredients/disks/create_from_source.py diff --git a/compute/compute/ingredients/disks/create_kms_encrypted_disk.py b/compute/client_library/ingredients/disks/create_kms_encrypted_disk.py similarity index 100% rename from compute/compute/ingredients/disks/create_kms_encrypted_disk.py rename to compute/client_library/ingredients/disks/create_kms_encrypted_disk.py diff --git a/compute/compute/ingredients/disks/delete.py b/compute/client_library/ingredients/disks/delete.py similarity index 100% rename from compute/compute/ingredients/disks/delete.py rename to compute/client_library/ingredients/disks/delete.py diff --git a/compute/compute/ingredients/disks/disk_from_snapshot.py b/compute/client_library/ingredients/disks/disk_from_snapshot.py similarity index 100% rename from compute/compute/ingredients/disks/disk_from_snapshot.py rename to compute/client_library/ingredients/disks/disk_from_snapshot.py diff --git a/compute/compute/ingredients/disks/empty_disk.py b/compute/client_library/ingredients/disks/empty_disk.py similarity index 100% rename from compute/compute/ingredients/disks/empty_disk.py rename to compute/client_library/ingredients/disks/empty_disk.py diff --git a/compute/compute/ingredients/disks/from_image.py b/compute/client_library/ingredients/disks/from_image.py similarity index 100% rename from compute/compute/ingredients/disks/from_image.py rename to compute/client_library/ingredients/disks/from_image.py diff --git a/compute/compute/ingredients/disks/get.py b/compute/client_library/ingredients/disks/get.py similarity index 100% rename from compute/compute/ingredients/disks/get.py rename to compute/client_library/ingredients/disks/get.py diff --git a/compute/compute/ingredients/disks/list.py b/compute/client_library/ingredients/disks/list.py similarity index 100% rename from compute/compute/ingredients/disks/list.py rename to compute/client_library/ingredients/disks/list.py diff --git a/compute/compute/ingredients/disks/local_ssd.py b/compute/client_library/ingredients/disks/local_ssd.py similarity index 100% rename from compute/compute/ingredients/disks/local_ssd.py rename to compute/client_library/ingredients/disks/local_ssd.py diff --git a/compute/compute/ingredients/disks/regional_create_from_source.py b/compute/client_library/ingredients/disks/regional_create_from_source.py similarity index 100% rename from compute/compute/ingredients/disks/regional_create_from_source.py rename to compute/client_library/ingredients/disks/regional_create_from_source.py diff --git a/compute/compute/ingredients/disks/regional_delete.py b/compute/client_library/ingredients/disks/regional_delete.py similarity index 100% rename from compute/compute/ingredients/disks/regional_delete.py rename to compute/client_library/ingredients/disks/regional_delete.py diff --git a/compute/compute/ingredients/firewall/create.py b/compute/client_library/ingredients/firewall/create.py similarity index 100% rename from compute/compute/ingredients/firewall/create.py rename to compute/client_library/ingredients/firewall/create.py diff --git a/compute/compute/ingredients/firewall/delete.py b/compute/client_library/ingredients/firewall/delete.py similarity index 100% rename from compute/compute/ingredients/firewall/delete.py rename to compute/client_library/ingredients/firewall/delete.py diff --git a/compute/compute/ingredients/firewall/get.py b/compute/client_library/ingredients/firewall/get.py similarity index 100% rename from compute/compute/ingredients/firewall/get.py rename to compute/client_library/ingredients/firewall/get.py diff --git a/compute/compute/ingredients/firewall/list.py b/compute/client_library/ingredients/firewall/list.py similarity index 100% rename from compute/compute/ingredients/firewall/list.py rename to compute/client_library/ingredients/firewall/list.py diff --git a/compute/compute/ingredients/firewall/patch.py b/compute/client_library/ingredients/firewall/patch.py similarity index 100% rename from compute/compute/ingredients/firewall/patch.py rename to compute/client_library/ingredients/firewall/patch.py diff --git a/compute/compute/ingredients/firewall/windows_kms.py b/compute/client_library/ingredients/firewall/windows_kms.py similarity index 100% rename from compute/compute/ingredients/firewall/windows_kms.py rename to compute/client_library/ingredients/firewall/windows_kms.py diff --git a/compute/compute/ingredients/images/create.py b/compute/client_library/ingredients/images/create.py similarity index 100% rename from compute/compute/ingredients/images/create.py rename to compute/client_library/ingredients/images/create.py diff --git a/compute/compute/ingredients/images/create_from_image.py b/compute/client_library/ingredients/images/create_from_image.py similarity index 100% rename from compute/compute/ingredients/images/create_from_image.py rename to compute/client_library/ingredients/images/create_from_image.py diff --git a/compute/compute/ingredients/images/create_from_snapshot.py b/compute/client_library/ingredients/images/create_from_snapshot.py similarity index 100% rename from compute/compute/ingredients/images/create_from_snapshot.py rename to compute/client_library/ingredients/images/create_from_snapshot.py diff --git a/compute/compute/ingredients/images/delete.py b/compute/client_library/ingredients/images/delete.py similarity index 100% rename from compute/compute/ingredients/images/delete.py rename to compute/client_library/ingredients/images/delete.py diff --git a/compute/compute/ingredients/images/get_image.py b/compute/client_library/ingredients/images/get_image.py similarity index 100% rename from compute/compute/ingredients/images/get_image.py rename to compute/client_library/ingredients/images/get_image.py diff --git a/compute/compute/ingredients/images/get_image_from_family.py b/compute/client_library/ingredients/images/get_image_from_family.py similarity index 100% rename from compute/compute/ingredients/images/get_image_from_family.py rename to compute/client_library/ingredients/images/get_image_from_family.py diff --git a/compute/compute/ingredients/images/list_images.py b/compute/client_library/ingredients/images/list_images.py similarity index 100% rename from compute/compute/ingredients/images/list_images.py rename to compute/client_library/ingredients/images/list_images.py diff --git a/compute/compute/ingredients/images/set_depracation_status.py b/compute/client_library/ingredients/images/set_depracation_status.py similarity index 100% rename from compute/compute/ingredients/images/set_depracation_status.py rename to compute/client_library/ingredients/images/set_depracation_status.py diff --git a/compute/compute/ingredients/instance-templates/create.py b/compute/client_library/ingredients/instance-templates/create.py similarity index 100% rename from compute/compute/ingredients/instance-templates/create.py rename to compute/client_library/ingredients/instance-templates/create.py diff --git a/compute/compute/ingredients/instance-templates/create_from_instance.py b/compute/client_library/ingredients/instance-templates/create_from_instance.py similarity index 100% rename from compute/compute/ingredients/instance-templates/create_from_instance.py rename to compute/client_library/ingredients/instance-templates/create_from_instance.py diff --git a/compute/compute/ingredients/instance-templates/create_with_subnet.py b/compute/client_library/ingredients/instance-templates/create_with_subnet.py similarity index 100% rename from compute/compute/ingredients/instance-templates/create_with_subnet.py rename to compute/client_library/ingredients/instance-templates/create_with_subnet.py diff --git a/compute/compute/ingredients/instance-templates/delete.py b/compute/client_library/ingredients/instance-templates/delete.py similarity index 100% rename from compute/compute/ingredients/instance-templates/delete.py rename to compute/client_library/ingredients/instance-templates/delete.py diff --git a/compute/compute/ingredients/instance-templates/get.py b/compute/client_library/ingredients/instance-templates/get.py similarity index 100% rename from compute/compute/ingredients/instance-templates/get.py rename to compute/client_library/ingredients/instance-templates/get.py diff --git a/compute/compute/ingredients/instance-templates/list.py b/compute/client_library/ingredients/instance-templates/list.py similarity index 100% rename from compute/compute/ingredients/instance-templates/list.py rename to compute/client_library/ingredients/instance-templates/list.py diff --git a/compute/compute/ingredients/instances/__init__.py b/compute/client_library/ingredients/instances/__init__.py similarity index 100% rename from compute/compute/ingredients/instances/__init__.py rename to compute/client_library/ingredients/instances/__init__.py diff --git a/compute/compute/ingredients/instances/bulk_insert.py b/compute/client_library/ingredients/instances/bulk_insert.py similarity index 100% rename from compute/compute/ingredients/instances/bulk_insert.py rename to compute/client_library/ingredients/instances/bulk_insert.py diff --git a/compute/compute/ingredients/instances/create_instance.py b/compute/client_library/ingredients/instances/create_instance.py similarity index 100% rename from compute/compute/ingredients/instances/create_instance.py rename to compute/client_library/ingredients/instances/create_instance.py diff --git a/compute/compute/ingredients/instances/create_instance_from_template.py b/compute/client_library/ingredients/instances/create_instance_from_template.py similarity index 100% rename from compute/compute/ingredients/instances/create_instance_from_template.py rename to compute/client_library/ingredients/instances/create_instance_from_template.py diff --git a/compute/compute/ingredients/instances/create_instance_from_template_with_overrides.py b/compute/client_library/ingredients/instances/create_instance_from_template_with_overrides.py similarity index 100% rename from compute/compute/ingredients/instances/create_instance_from_template_with_overrides.py rename to compute/client_library/ingredients/instances/create_instance_from_template_with_overrides.py diff --git a/compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.py b/compute/client_library/ingredients/instances/create_start_instance/create_from_custom_image.py similarity index 100% rename from compute/compute/ingredients/instances/create_start_instance/create_from_custom_image.py rename to compute/client_library/ingredients/instances/create_start_instance/create_from_custom_image.py diff --git a/compute/compute/ingredients/instances/create_start_instance/create_from_public_image.py b/compute/client_library/ingredients/instances/create_start_instance/create_from_public_image.py similarity index 100% rename from compute/compute/ingredients/instances/create_start_instance/create_from_public_image.py rename to compute/client_library/ingredients/instances/create_start_instance/create_from_public_image.py diff --git a/compute/compute/ingredients/instances/create_start_instance/create_from_snapshot.py b/compute/client_library/ingredients/instances/create_start_instance/create_from_snapshot.py similarity index 100% rename from compute/compute/ingredients/instances/create_start_instance/create_from_snapshot.py rename to compute/client_library/ingredients/instances/create_start_instance/create_from_snapshot.py diff --git a/compute/compute/ingredients/instances/create_start_instance/create_windows_instance.py b/compute/client_library/ingredients/instances/create_start_instance/create_windows_instance.py similarity index 100% rename from compute/compute/ingredients/instances/create_start_instance/create_windows_instance.py rename to compute/client_library/ingredients/instances/create_start_instance/create_windows_instance.py diff --git a/compute/compute/ingredients/instances/create_start_instance/create_with_additional_disk.py b/compute/client_library/ingredients/instances/create_start_instance/create_with_additional_disk.py similarity index 100% rename from compute/compute/ingredients/instances/create_start_instance/create_with_additional_disk.py rename to compute/client_library/ingredients/instances/create_start_instance/create_with_additional_disk.py diff --git a/compute/compute/ingredients/instances/create_start_instance/create_with_existing_disks.py b/compute/client_library/ingredients/instances/create_start_instance/create_with_existing_disks.py similarity index 100% rename from compute/compute/ingredients/instances/create_start_instance/create_with_existing_disks.py rename to compute/client_library/ingredients/instances/create_start_instance/create_with_existing_disks.py diff --git a/compute/compute/ingredients/instances/create_start_instance/create_with_local_ssd.py b/compute/client_library/ingredients/instances/create_start_instance/create_with_local_ssd.py similarity index 100% rename from compute/compute/ingredients/instances/create_start_instance/create_with_local_ssd.py rename to compute/client_library/ingredients/instances/create_start_instance/create_with_local_ssd.py diff --git a/compute/compute/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 similarity index 100% rename from compute/compute/ingredients/instances/create_start_instance/create_with_snapshotted_data_disk.py rename to compute/client_library/ingredients/instances/create_start_instance/create_with_snapshotted_data_disk.py diff --git a/compute/compute/ingredients/instances/create_with_subnet.py b/compute/client_library/ingredients/instances/create_with_subnet.py similarity index 100% rename from compute/compute/ingredients/instances/create_with_subnet.py rename to compute/client_library/ingredients/instances/create_with_subnet.py diff --git a/compute/compute/ingredients/instances/custom_hostname/create.py b/compute/client_library/ingredients/instances/custom_hostname/create.py similarity index 100% rename from compute/compute/ingredients/instances/custom_hostname/create.py rename to compute/client_library/ingredients/instances/custom_hostname/create.py diff --git a/compute/compute/ingredients/instances/custom_hostname/get.py b/compute/client_library/ingredients/instances/custom_hostname/get.py similarity index 100% rename from compute/compute/ingredients/instances/custom_hostname/get.py rename to compute/client_library/ingredients/instances/custom_hostname/get.py diff --git a/compute/compute/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 similarity index 100% rename from compute/compute/ingredients/instances/custom_machine_types/create_extra_mem_no_helper.py rename to compute/client_library/ingredients/instances/custom_machine_types/create_extra_mem_no_helper.py diff --git a/compute/compute/ingredients/instances/custom_machine_types/create_shared_with_helper.py b/compute/client_library/ingredients/instances/custom_machine_types/create_shared_with_helper.py similarity index 100% rename from compute/compute/ingredients/instances/custom_machine_types/create_shared_with_helper.py rename to compute/client_library/ingredients/instances/custom_machine_types/create_shared_with_helper.py diff --git a/compute/compute/ingredients/instances/custom_machine_types/create_with_helper.py b/compute/client_library/ingredients/instances/custom_machine_types/create_with_helper.py similarity index 100% rename from compute/compute/ingredients/instances/custom_machine_types/create_with_helper.py rename to compute/client_library/ingredients/instances/custom_machine_types/create_with_helper.py diff --git a/compute/compute/ingredients/instances/custom_machine_types/create_without_helper.py b/compute/client_library/ingredients/instances/custom_machine_types/create_without_helper.py similarity index 100% rename from compute/compute/ingredients/instances/custom_machine_types/create_without_helper.py rename to compute/client_library/ingredients/instances/custom_machine_types/create_without_helper.py diff --git a/compute/compute/ingredients/instances/custom_machine_types/helper_class.py b/compute/client_library/ingredients/instances/custom_machine_types/helper_class.py similarity index 100% rename from compute/compute/ingredients/instances/custom_machine_types/helper_class.py rename to compute/client_library/ingredients/instances/custom_machine_types/helper_class.py diff --git a/compute/compute/ingredients/instances/custom_machine_types/update_memory.py b/compute/client_library/ingredients/instances/custom_machine_types/update_memory.py similarity index 100% rename from compute/compute/ingredients/instances/custom_machine_types/update_memory.py rename to compute/client_library/ingredients/instances/custom_machine_types/update_memory.py diff --git a/compute/compute/ingredients/instances/delete.py b/compute/client_library/ingredients/instances/delete.py similarity index 100% rename from compute/compute/ingredients/instances/delete.py rename to compute/client_library/ingredients/instances/delete.py diff --git a/compute/compute/ingredients/instances/delete_protection/__init__.py b/compute/client_library/ingredients/instances/delete_protection/__init__.py similarity index 100% rename from compute/compute/ingredients/instances/delete_protection/__init__.py rename to compute/client_library/ingredients/instances/delete_protection/__init__.py diff --git a/compute/compute/ingredients/instances/delete_protection/create.py b/compute/client_library/ingredients/instances/delete_protection/create.py similarity index 100% rename from compute/compute/ingredients/instances/delete_protection/create.py rename to compute/client_library/ingredients/instances/delete_protection/create.py diff --git a/compute/compute/ingredients/instances/delete_protection/get.py b/compute/client_library/ingredients/instances/delete_protection/get.py similarity index 100% rename from compute/compute/ingredients/instances/delete_protection/get.py rename to compute/client_library/ingredients/instances/delete_protection/get.py diff --git a/compute/compute/ingredients/instances/delete_protection/set.py b/compute/client_library/ingredients/instances/delete_protection/set.py similarity index 100% rename from compute/compute/ingredients/instances/delete_protection/set.py rename to compute/client_library/ingredients/instances/delete_protection/set.py diff --git a/compute/compute/ingredients/instances/get.py b/compute/client_library/ingredients/instances/get.py similarity index 100% rename from compute/compute/ingredients/instances/get.py rename to compute/client_library/ingredients/instances/get.py diff --git a/compute/compute/ingredients/instances/get_serial_port.py b/compute/client_library/ingredients/instances/get_serial_port.py similarity index 100% rename from compute/compute/ingredients/instances/get_serial_port.py rename to compute/client_library/ingredients/instances/get_serial_port.py diff --git a/compute/compute/ingredients/instances/list.py b/compute/client_library/ingredients/instances/list.py similarity index 100% rename from compute/compute/ingredients/instances/list.py rename to compute/client_library/ingredients/instances/list.py diff --git a/compute/compute/ingredients/instances/list_all.py b/compute/client_library/ingredients/instances/list_all.py similarity index 100% rename from compute/compute/ingredients/instances/list_all.py rename to compute/client_library/ingredients/instances/list_all.py diff --git a/compute/compute/ingredients/instances/preemptible/__init__.py b/compute/client_library/ingredients/instances/preemptible/__init__.py similarity index 100% rename from compute/compute/ingredients/instances/preemptible/__init__.py rename to compute/client_library/ingredients/instances/preemptible/__init__.py diff --git a/compute/compute/ingredients/instances/preemptible/create.py b/compute/client_library/ingredients/instances/preemptible/create.py similarity index 100% rename from compute/compute/ingredients/instances/preemptible/create.py rename to compute/client_library/ingredients/instances/preemptible/create.py diff --git a/compute/compute/ingredients/instances/preemptible/get.py b/compute/client_library/ingredients/instances/preemptible/get.py similarity index 100% rename from compute/compute/ingredients/instances/preemptible/get.py rename to compute/client_library/ingredients/instances/preemptible/get.py diff --git a/compute/compute/ingredients/instances/preemptible/preemption_history.py b/compute/client_library/ingredients/instances/preemptible/preemption_history.py similarity index 100% rename from compute/compute/ingredients/instances/preemptible/preemption_history.py rename to compute/client_library/ingredients/instances/preemptible/preemption_history.py diff --git a/compute/compute/ingredients/instances/reset.py b/compute/client_library/ingredients/instances/reset.py similarity index 100% rename from compute/compute/ingredients/instances/reset.py rename to compute/client_library/ingredients/instances/reset.py diff --git a/compute/compute/ingredients/instances/resume.py b/compute/client_library/ingredients/instances/resume.py similarity index 100% rename from compute/compute/ingredients/instances/resume.py rename to compute/client_library/ingredients/instances/resume.py diff --git a/compute/compute/ingredients/instances/spot/create.py b/compute/client_library/ingredients/instances/spot/create.py similarity index 100% rename from compute/compute/ingredients/instances/spot/create.py rename to compute/client_library/ingredients/instances/spot/create.py diff --git a/compute/compute/ingredients/instances/spot/get.py b/compute/client_library/ingredients/instances/spot/get.py similarity index 100% rename from compute/compute/ingredients/instances/spot/get.py rename to compute/client_library/ingredients/instances/spot/get.py diff --git a/compute/compute/ingredients/instances/start.py b/compute/client_library/ingredients/instances/start.py similarity index 100% rename from compute/compute/ingredients/instances/start.py rename to compute/client_library/ingredients/instances/start.py diff --git a/compute/compute/ingredients/instances/start_encrypted.py b/compute/client_library/ingredients/instances/start_encrypted.py similarity index 100% rename from compute/compute/ingredients/instances/start_encrypted.py rename to compute/client_library/ingredients/instances/start_encrypted.py diff --git a/compute/compute/ingredients/instances/stop.py b/compute/client_library/ingredients/instances/stop.py similarity index 100% rename from compute/compute/ingredients/instances/stop.py rename to compute/client_library/ingredients/instances/stop.py diff --git a/compute/compute/ingredients/instances/suspend.py b/compute/client_library/ingredients/instances/suspend.py similarity index 100% rename from compute/compute/ingredients/instances/suspend.py rename to compute/client_library/ingredients/instances/suspend.py diff --git a/compute/compute/ingredients/operations/__init__.py b/compute/client_library/ingredients/operations/__init__.py similarity index 100% rename from compute/compute/ingredients/operations/__init__.py rename to compute/client_library/ingredients/operations/__init__.py diff --git a/compute/compute/ingredients/operations/handle_extended_operation.py b/compute/client_library/ingredients/operations/handle_extended_operation.py similarity index 100% rename from compute/compute/ingredients/operations/handle_extended_operation.py rename to compute/client_library/ingredients/operations/handle_extended_operation.py diff --git a/compute/compute/ingredients/operations/list_zone_operations.py b/compute/client_library/ingredients/operations/list_zone_operations.py similarity index 100% rename from compute/compute/ingredients/operations/list_zone_operations.py rename to compute/client_library/ingredients/operations/list_zone_operations.py diff --git a/compute/compute/ingredients/operations/wait_for_operation.py b/compute/client_library/ingredients/operations/wait_for_operation.py similarity index 100% rename from compute/compute/ingredients/operations/wait_for_operation.py rename to compute/client_library/ingredients/operations/wait_for_operation.py diff --git a/compute/compute/ingredients/routes/create.py b/compute/client_library/ingredients/routes/create.py similarity index 100% rename from compute/compute/ingredients/routes/create.py rename to compute/client_library/ingredients/routes/create.py diff --git a/compute/compute/ingredients/routes/delete.py b/compute/client_library/ingredients/routes/delete.py similarity index 100% rename from compute/compute/ingredients/routes/delete.py rename to compute/client_library/ingredients/routes/delete.py diff --git a/compute/compute/ingredients/routes/list.py b/compute/client_library/ingredients/routes/list.py similarity index 100% rename from compute/compute/ingredients/routes/list.py rename to compute/client_library/ingredients/routes/list.py diff --git a/compute/compute/ingredients/snapshots/create.py b/compute/client_library/ingredients/snapshots/create.py similarity index 100% rename from compute/compute/ingredients/snapshots/create.py rename to compute/client_library/ingredients/snapshots/create.py diff --git a/compute/compute/ingredients/snapshots/delete.py b/compute/client_library/ingredients/snapshots/delete.py similarity index 100% rename from compute/compute/ingredients/snapshots/delete.py rename to compute/client_library/ingredients/snapshots/delete.py diff --git a/compute/compute/ingredients/snapshots/get.py b/compute/client_library/ingredients/snapshots/get.py similarity index 100% rename from compute/compute/ingredients/snapshots/get.py rename to compute/client_library/ingredients/snapshots/get.py diff --git a/compute/compute/ingredients/snapshots/list.py b/compute/client_library/ingredients/snapshots/list.py similarity index 100% rename from compute/compute/ingredients/snapshots/list.py rename to compute/client_library/ingredients/snapshots/list.py diff --git a/compute/compute/ingredients/usage_report/disable.py b/compute/client_library/ingredients/usage_report/disable.py similarity index 100% rename from compute/compute/ingredients/usage_report/disable.py rename to compute/client_library/ingredients/usage_report/disable.py diff --git a/compute/compute/ingredients/usage_report/get_bucket.py b/compute/client_library/ingredients/usage_report/get_bucket.py similarity index 100% rename from compute/compute/ingredients/usage_report/get_bucket.py rename to compute/client_library/ingredients/usage_report/get_bucket.py diff --git a/compute/compute/ingredients/usage_report/set_bucket.py b/compute/client_library/ingredients/usage_report/set_bucket.py similarity index 100% rename from compute/compute/ingredients/usage_report/set_bucket.py rename to compute/client_library/ingredients/usage_report/set_bucket.py diff --git a/compute/compute/noxfile_config.py b/compute/client_library/noxfile_config.py similarity index 100% rename from compute/compute/noxfile_config.py rename to compute/client_library/noxfile_config.py diff --git a/compute/compute/recipes/__init__.py b/compute/client_library/recipes/__init__.py similarity index 100% rename from compute/compute/recipes/__init__.py rename to compute/client_library/recipes/__init__.py diff --git a/compute/compute/recipes/disks/__init__.py b/compute/client_library/recipes/disks/__init__.py similarity index 100% rename from compute/compute/recipes/disks/__init__.py rename to compute/client_library/recipes/disks/__init__.py diff --git a/compute/compute/recipes/disks/autodelete_change.py b/compute/client_library/recipes/disks/autodelete_change.py similarity index 100% rename from compute/compute/recipes/disks/autodelete_change.py rename to compute/client_library/recipes/disks/autodelete_change.py diff --git a/compute/compute/recipes/disks/clone_encrypted_disk.py b/compute/client_library/recipes/disks/clone_encrypted_disk.py similarity index 100% rename from compute/compute/recipes/disks/clone_encrypted_disk.py rename to compute/client_library/recipes/disks/clone_encrypted_disk.py diff --git a/compute/compute/recipes/disks/clone_encrypted_disk_managed_key.py b/compute/client_library/recipes/disks/clone_encrypted_disk_managed_key.py similarity index 100% rename from compute/compute/recipes/disks/clone_encrypted_disk_managed_key.py rename to compute/client_library/recipes/disks/clone_encrypted_disk_managed_key.py diff --git a/compute/compute/recipes/disks/create_empty_disk.py b/compute/client_library/recipes/disks/create_empty_disk.py similarity index 100% rename from compute/compute/recipes/disks/create_empty_disk.py rename to compute/client_library/recipes/disks/create_empty_disk.py diff --git a/compute/compute/recipes/disks/create_from_image.py b/compute/client_library/recipes/disks/create_from_image.py similarity index 100% rename from compute/compute/recipes/disks/create_from_image.py rename to compute/client_library/recipes/disks/create_from_image.py diff --git a/compute/compute/recipes/disks/create_from_snapshot.py b/compute/client_library/recipes/disks/create_from_snapshot.py similarity index 100% rename from compute/compute/recipes/disks/create_from_snapshot.py rename to compute/client_library/recipes/disks/create_from_snapshot.py diff --git a/compute/compute/recipes/disks/create_from_source.py b/compute/client_library/recipes/disks/create_from_source.py similarity index 100% rename from compute/compute/recipes/disks/create_from_source.py rename to compute/client_library/recipes/disks/create_from_source.py diff --git a/compute/compute/recipes/disks/create_kms_encrypted_disk.py b/compute/client_library/recipes/disks/create_kms_encrypted_disk.py similarity index 100% rename from compute/compute/recipes/disks/create_kms_encrypted_disk.py rename to compute/client_library/recipes/disks/create_kms_encrypted_disk.py diff --git a/compute/compute/recipes/disks/delete.py b/compute/client_library/recipes/disks/delete.py similarity index 100% rename from compute/compute/recipes/disks/delete.py rename to compute/client_library/recipes/disks/delete.py diff --git a/compute/compute/recipes/disks/list.py b/compute/client_library/recipes/disks/list.py similarity index 100% rename from compute/compute/recipes/disks/list.py rename to compute/client_library/recipes/disks/list.py diff --git a/compute/compute/recipes/disks/regional_create_from_source.py b/compute/client_library/recipes/disks/regional_create_from_source.py similarity index 100% rename from compute/compute/recipes/disks/regional_create_from_source.py rename to compute/client_library/recipes/disks/regional_create_from_source.py diff --git a/compute/compute/recipes/disks/regional_delete.py b/compute/client_library/recipes/disks/regional_delete.py similarity index 100% rename from compute/compute/recipes/disks/regional_delete.py rename to compute/client_library/recipes/disks/regional_delete.py diff --git a/compute/compute/recipes/firewall/__init__.py b/compute/client_library/recipes/firewall/__init__.py similarity index 100% rename from compute/compute/recipes/firewall/__init__.py rename to compute/client_library/recipes/firewall/__init__.py diff --git a/compute/compute/recipes/firewall/create.py b/compute/client_library/recipes/firewall/create.py similarity index 100% rename from compute/compute/recipes/firewall/create.py rename to compute/client_library/recipes/firewall/create.py diff --git a/compute/compute/recipes/firewall/delete.py b/compute/client_library/recipes/firewall/delete.py similarity index 100% rename from compute/compute/recipes/firewall/delete.py rename to compute/client_library/recipes/firewall/delete.py diff --git a/compute/compute/recipes/firewall/list.py b/compute/client_library/recipes/firewall/list.py similarity index 100% rename from compute/compute/recipes/firewall/list.py rename to compute/client_library/recipes/firewall/list.py diff --git a/compute/compute/recipes/firewall/main.py b/compute/client_library/recipes/firewall/main.py similarity index 100% rename from compute/compute/recipes/firewall/main.py rename to compute/client_library/recipes/firewall/main.py diff --git a/compute/compute/recipes/firewall/patch.py b/compute/client_library/recipes/firewall/patch.py similarity index 100% rename from compute/compute/recipes/firewall/patch.py rename to compute/client_library/recipes/firewall/patch.py diff --git a/compute/compute/recipes/firewall/windows_kms.py b/compute/client_library/recipes/firewall/windows_kms.py similarity index 100% rename from compute/compute/recipes/firewall/windows_kms.py rename to compute/client_library/recipes/firewall/windows_kms.py diff --git a/compute/compute/recipes/images/__init__.py b/compute/client_library/recipes/images/__init__.py similarity index 100% rename from compute/compute/recipes/images/__init__.py rename to compute/client_library/recipes/images/__init__.py diff --git a/compute/compute/recipes/images/create.py b/compute/client_library/recipes/images/create.py similarity index 100% rename from compute/compute/recipes/images/create.py rename to compute/client_library/recipes/images/create.py diff --git a/compute/compute/recipes/images/create_from_image.py b/compute/client_library/recipes/images/create_from_image.py similarity index 100% rename from compute/compute/recipes/images/create_from_image.py rename to compute/client_library/recipes/images/create_from_image.py diff --git a/compute/compute/recipes/images/create_from_snapshot.py b/compute/client_library/recipes/images/create_from_snapshot.py similarity index 100% rename from compute/compute/recipes/images/create_from_snapshot.py rename to compute/client_library/recipes/images/create_from_snapshot.py diff --git a/compute/compute/recipes/images/delete.py b/compute/client_library/recipes/images/delete.py similarity index 100% rename from compute/compute/recipes/images/delete.py rename to compute/client_library/recipes/images/delete.py diff --git a/compute/compute/recipes/images/get.py b/compute/client_library/recipes/images/get.py similarity index 100% rename from compute/compute/recipes/images/get.py rename to compute/client_library/recipes/images/get.py diff --git a/compute/compute/recipes/images/list.py b/compute/client_library/recipes/images/list.py similarity index 100% rename from compute/compute/recipes/images/list.py rename to compute/client_library/recipes/images/list.py diff --git a/compute/compute/recipes/images/pagination.py b/compute/client_library/recipes/images/pagination.py similarity index 100% rename from compute/compute/recipes/images/pagination.py rename to compute/client_library/recipes/images/pagination.py diff --git a/compute/compute/recipes/images/set_deprecation_status.py b/compute/client_library/recipes/images/set_deprecation_status.py similarity index 100% rename from compute/compute/recipes/images/set_deprecation_status.py rename to compute/client_library/recipes/images/set_deprecation_status.py diff --git a/compute/compute/recipes/instance_templates/__init__.py b/compute/client_library/recipes/instance_templates/__init__.py similarity index 100% rename from compute/compute/recipes/instance_templates/__init__.py rename to compute/client_library/recipes/instance_templates/__init__.py diff --git a/compute/compute/recipes/instance_templates/create.py b/compute/client_library/recipes/instance_templates/create.py similarity index 100% rename from compute/compute/recipes/instance_templates/create.py rename to compute/client_library/recipes/instance_templates/create.py diff --git a/compute/compute/recipes/instance_templates/create_from_instance.py b/compute/client_library/recipes/instance_templates/create_from_instance.py similarity index 100% rename from compute/compute/recipes/instance_templates/create_from_instance.py rename to compute/client_library/recipes/instance_templates/create_from_instance.py diff --git a/compute/compute/recipes/instance_templates/create_with_subnet.py b/compute/client_library/recipes/instance_templates/create_with_subnet.py similarity index 100% rename from compute/compute/recipes/instance_templates/create_with_subnet.py rename to compute/client_library/recipes/instance_templates/create_with_subnet.py diff --git a/compute/compute/recipes/instance_templates/delete.py b/compute/client_library/recipes/instance_templates/delete.py similarity index 100% rename from compute/compute/recipes/instance_templates/delete.py rename to compute/client_library/recipes/instance_templates/delete.py diff --git a/compute/compute/recipes/instance_templates/get.py b/compute/client_library/recipes/instance_templates/get.py similarity index 100% rename from compute/compute/recipes/instance_templates/get.py rename to compute/client_library/recipes/instance_templates/get.py diff --git a/compute/compute/recipes/instance_templates/list.py b/compute/client_library/recipes/instance_templates/list.py similarity index 100% rename from compute/compute/recipes/instance_templates/list.py rename to compute/client_library/recipes/instance_templates/list.py diff --git a/compute/compute/recipes/instances/__init__.py b/compute/client_library/recipes/instances/__init__.py similarity index 100% rename from compute/compute/recipes/instances/__init__.py rename to compute/client_library/recipes/instances/__init__.py diff --git a/compute/compute/recipes/instances/bulk_insert.py b/compute/client_library/recipes/instances/bulk_insert.py similarity index 100% rename from compute/compute/recipes/instances/bulk_insert.py rename to compute/client_library/recipes/instances/bulk_insert.py diff --git a/compute/compute/recipes/instances/create.py b/compute/client_library/recipes/instances/create.py similarity index 100% rename from compute/compute/recipes/instances/create.py rename to compute/client_library/recipes/instances/create.py diff --git a/compute/compute/recipes/instances/create_start_instance/__init__.py b/compute/client_library/recipes/instances/create_start_instance/__init__.py similarity index 100% rename from compute/compute/recipes/instances/create_start_instance/__init__.py rename to compute/client_library/recipes/instances/create_start_instance/__init__.py diff --git a/compute/compute/recipes/instances/create_start_instance/create_from_custom_image.py b/compute/client_library/recipes/instances/create_start_instance/create_from_custom_image.py similarity index 100% rename from compute/compute/recipes/instances/create_start_instance/create_from_custom_image.py rename to compute/client_library/recipes/instances/create_start_instance/create_from_custom_image.py diff --git a/compute/compute/recipes/instances/create_start_instance/create_from_public_image.py b/compute/client_library/recipes/instances/create_start_instance/create_from_public_image.py similarity index 100% rename from compute/compute/recipes/instances/create_start_instance/create_from_public_image.py rename to compute/client_library/recipes/instances/create_start_instance/create_from_public_image.py diff --git a/compute/compute/recipes/instances/create_start_instance/create_from_snapshot.py b/compute/client_library/recipes/instances/create_start_instance/create_from_snapshot.py similarity index 100% rename from compute/compute/recipes/instances/create_start_instance/create_from_snapshot.py rename to compute/client_library/recipes/instances/create_start_instance/create_from_snapshot.py diff --git a/compute/compute/recipes/instances/create_start_instance/create_windows_instance.py b/compute/client_library/recipes/instances/create_start_instance/create_windows_instance.py similarity index 100% rename from compute/compute/recipes/instances/create_start_instance/create_windows_instance.py rename to compute/client_library/recipes/instances/create_start_instance/create_windows_instance.py diff --git a/compute/compute/recipes/instances/create_start_instance/create_with_additional_disk.py b/compute/client_library/recipes/instances/create_start_instance/create_with_additional_disk.py similarity index 100% rename from compute/compute/recipes/instances/create_start_instance/create_with_additional_disk.py rename to compute/client_library/recipes/instances/create_start_instance/create_with_additional_disk.py diff --git a/compute/compute/recipes/instances/create_start_instance/create_with_existing_disks.py b/compute/client_library/recipes/instances/create_start_instance/create_with_existing_disks.py similarity index 100% rename from compute/compute/recipes/instances/create_start_instance/create_with_existing_disks.py rename to compute/client_library/recipes/instances/create_start_instance/create_with_existing_disks.py diff --git a/compute/compute/recipes/instances/create_start_instance/create_with_local_ssd.py b/compute/client_library/recipes/instances/create_start_instance/create_with_local_ssd.py similarity index 100% rename from compute/compute/recipes/instances/create_start_instance/create_with_local_ssd.py rename to compute/client_library/recipes/instances/create_start_instance/create_with_local_ssd.py diff --git a/compute/compute/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 similarity index 100% rename from compute/compute/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.py rename to compute/client_library/recipes/instances/create_start_instance/create_with_snapshotted_data_disk.py diff --git a/compute/compute/recipes/instances/create_with_subnet.py b/compute/client_library/recipes/instances/create_with_subnet.py similarity index 100% rename from compute/compute/recipes/instances/create_with_subnet.py rename to compute/client_library/recipes/instances/create_with_subnet.py diff --git a/compute/compute/recipes/instances/custom_hostname/create.py b/compute/client_library/recipes/instances/custom_hostname/create.py similarity index 100% rename from compute/compute/recipes/instances/custom_hostname/create.py rename to compute/client_library/recipes/instances/custom_hostname/create.py diff --git a/compute/compute/recipes/instances/custom_hostname/get.py b/compute/client_library/recipes/instances/custom_hostname/get.py similarity index 100% rename from compute/compute/recipes/instances/custom_hostname/get.py rename to compute/client_library/recipes/instances/custom_hostname/get.py diff --git a/compute/compute/recipes/instances/custom_machine_types/__init__.py b/compute/client_library/recipes/instances/custom_machine_types/__init__.py similarity index 100% rename from compute/compute/recipes/instances/custom_machine_types/__init__.py rename to compute/client_library/recipes/instances/custom_machine_types/__init__.py diff --git a/compute/compute/recipes/instances/custom_machine_types/create_shared_with_helper.py b/compute/client_library/recipes/instances/custom_machine_types/create_shared_with_helper.py similarity index 100% rename from compute/compute/recipes/instances/custom_machine_types/create_shared_with_helper.py rename to compute/client_library/recipes/instances/custom_machine_types/create_shared_with_helper.py diff --git a/compute/compute/recipes/instances/custom_machine_types/create_with_helper.py b/compute/client_library/recipes/instances/custom_machine_types/create_with_helper.py similarity index 100% rename from compute/compute/recipes/instances/custom_machine_types/create_with_helper.py rename to compute/client_library/recipes/instances/custom_machine_types/create_with_helper.py diff --git a/compute/compute/recipes/instances/custom_machine_types/create_without_helper.py b/compute/client_library/recipes/instances/custom_machine_types/create_without_helper.py similarity index 100% rename from compute/compute/recipes/instances/custom_machine_types/create_without_helper.py rename to compute/client_library/recipes/instances/custom_machine_types/create_without_helper.py diff --git a/compute/compute/recipes/instances/custom_machine_types/extra_mem_no_helper.py b/compute/client_library/recipes/instances/custom_machine_types/extra_mem_no_helper.py similarity index 100% rename from compute/compute/recipes/instances/custom_machine_types/extra_mem_no_helper.py rename to compute/client_library/recipes/instances/custom_machine_types/extra_mem_no_helper.py diff --git a/compute/compute/recipes/instances/custom_machine_types/helper_class.py b/compute/client_library/recipes/instances/custom_machine_types/helper_class.py similarity index 100% rename from compute/compute/recipes/instances/custom_machine_types/helper_class.py rename to compute/client_library/recipes/instances/custom_machine_types/helper_class.py diff --git a/compute/compute/recipes/instances/custom_machine_types/update_memory.py b/compute/client_library/recipes/instances/custom_machine_types/update_memory.py similarity index 100% rename from compute/compute/recipes/instances/custom_machine_types/update_memory.py rename to compute/client_library/recipes/instances/custom_machine_types/update_memory.py diff --git a/compute/compute/recipes/instances/delete.py b/compute/client_library/recipes/instances/delete.py similarity index 100% rename from compute/compute/recipes/instances/delete.py rename to compute/client_library/recipes/instances/delete.py diff --git a/compute/compute/recipes/instances/delete_protection/__init__.py b/compute/client_library/recipes/instances/delete_protection/__init__.py similarity index 100% rename from compute/compute/recipes/instances/delete_protection/__init__.py rename to compute/client_library/recipes/instances/delete_protection/__init__.py diff --git a/compute/compute/recipes/instances/delete_protection/create.py b/compute/client_library/recipes/instances/delete_protection/create.py similarity index 100% rename from compute/compute/recipes/instances/delete_protection/create.py rename to compute/client_library/recipes/instances/delete_protection/create.py diff --git a/compute/compute/recipes/instances/delete_protection/get.py b/compute/client_library/recipes/instances/delete_protection/get.py similarity index 100% rename from compute/compute/recipes/instances/delete_protection/get.py rename to compute/client_library/recipes/instances/delete_protection/get.py diff --git a/compute/compute/recipes/instances/delete_protection/set.py b/compute/client_library/recipes/instances/delete_protection/set.py similarity index 100% rename from compute/compute/recipes/instances/delete_protection/set.py rename to compute/client_library/recipes/instances/delete_protection/set.py diff --git a/compute/compute/recipes/instances/from_instance_template/__init__.py b/compute/client_library/recipes/instances/from_instance_template/__init__.py similarity index 100% rename from compute/compute/recipes/instances/from_instance_template/__init__.py rename to compute/client_library/recipes/instances/from_instance_template/__init__.py diff --git a/compute/compute/recipes/instances/from_instance_template/create_from_template.py b/compute/client_library/recipes/instances/from_instance_template/create_from_template.py similarity index 100% rename from compute/compute/recipes/instances/from_instance_template/create_from_template.py rename to compute/client_library/recipes/instances/from_instance_template/create_from_template.py diff --git a/compute/compute/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 similarity index 100% rename from compute/compute/recipes/instances/from_instance_template/create_from_template_with_overrides.py rename to compute/client_library/recipes/instances/from_instance_template/create_from_template_with_overrides.py diff --git a/compute/compute/recipes/instances/get.py b/compute/client_library/recipes/instances/get.py similarity index 100% rename from compute/compute/recipes/instances/get.py rename to compute/client_library/recipes/instances/get.py diff --git a/compute/compute/recipes/instances/list.py b/compute/client_library/recipes/instances/list.py similarity index 100% rename from compute/compute/recipes/instances/list.py rename to compute/client_library/recipes/instances/list.py diff --git a/compute/compute/recipes/instances/list_all.py b/compute/client_library/recipes/instances/list_all.py similarity index 100% rename from compute/compute/recipes/instances/list_all.py rename to compute/client_library/recipes/instances/list_all.py diff --git a/compute/compute/recipes/instances/preemptible/__init__.py b/compute/client_library/recipes/instances/preemptible/__init__.py similarity index 100% rename from compute/compute/recipes/instances/preemptible/__init__.py rename to compute/client_library/recipes/instances/preemptible/__init__.py diff --git a/compute/compute/recipes/instances/preemptible/create_preemptible.py b/compute/client_library/recipes/instances/preemptible/create_preemptible.py similarity index 100% rename from compute/compute/recipes/instances/preemptible/create_preemptible.py rename to compute/client_library/recipes/instances/preemptible/create_preemptible.py diff --git a/compute/compute/recipes/instances/preemptible/is_preemptible.py b/compute/client_library/recipes/instances/preemptible/is_preemptible.py similarity index 100% rename from compute/compute/recipes/instances/preemptible/is_preemptible.py rename to compute/client_library/recipes/instances/preemptible/is_preemptible.py diff --git a/compute/compute/recipes/instances/preemptible/preemption_history.py b/compute/client_library/recipes/instances/preemptible/preemption_history.py similarity index 100% rename from compute/compute/recipes/instances/preemptible/preemption_history.py rename to compute/client_library/recipes/instances/preemptible/preemption_history.py diff --git a/compute/compute/recipes/instances/reset.py b/compute/client_library/recipes/instances/reset.py similarity index 100% rename from compute/compute/recipes/instances/reset.py rename to compute/client_library/recipes/instances/reset.py diff --git a/compute/compute/recipes/instances/resume.py b/compute/client_library/recipes/instances/resume.py similarity index 100% rename from compute/compute/recipes/instances/resume.py rename to compute/client_library/recipes/instances/resume.py diff --git a/compute/compute/recipes/instances/spot/__init__.py b/compute/client_library/recipes/instances/spot/__init__.py similarity index 100% rename from compute/compute/recipes/instances/spot/__init__.py rename to compute/client_library/recipes/instances/spot/__init__.py diff --git a/compute/compute/recipes/instances/spot/create.py b/compute/client_library/recipes/instances/spot/create.py similarity index 100% rename from compute/compute/recipes/instances/spot/create.py rename to compute/client_library/recipes/instances/spot/create.py diff --git a/compute/compute/recipes/instances/spot/is_spot_vm.py b/compute/client_library/recipes/instances/spot/is_spot_vm.py similarity index 100% rename from compute/compute/recipes/instances/spot/is_spot_vm.py rename to compute/client_library/recipes/instances/spot/is_spot_vm.py diff --git a/compute/compute/recipes/instances/start.py b/compute/client_library/recipes/instances/start.py similarity index 100% rename from compute/compute/recipes/instances/start.py rename to compute/client_library/recipes/instances/start.py diff --git a/compute/compute/recipes/instances/start_encrypted.py b/compute/client_library/recipes/instances/start_encrypted.py similarity index 100% rename from compute/compute/recipes/instances/start_encrypted.py rename to compute/client_library/recipes/instances/start_encrypted.py diff --git a/compute/compute/recipes/instances/stop.py b/compute/client_library/recipes/instances/stop.py similarity index 100% rename from compute/compute/recipes/instances/stop.py rename to compute/client_library/recipes/instances/stop.py diff --git a/compute/compute/recipes/instances/suspend.py b/compute/client_library/recipes/instances/suspend.py similarity index 100% rename from compute/compute/recipes/instances/suspend.py rename to compute/client_library/recipes/instances/suspend.py diff --git a/compute/compute/recipes/operations/__init__.py b/compute/client_library/recipes/operations/__init__.py similarity index 100% rename from compute/compute/recipes/operations/__init__.py rename to compute/client_library/recipes/operations/__init__.py diff --git a/compute/compute/recipes/operations/operation_check.py b/compute/client_library/recipes/operations/operation_check.py similarity index 100% rename from compute/compute/recipes/operations/operation_check.py rename to compute/client_library/recipes/operations/operation_check.py diff --git a/compute/compute/recipes/routes/create.py b/compute/client_library/recipes/routes/create.py similarity index 100% rename from compute/compute/recipes/routes/create.py rename to compute/client_library/recipes/routes/create.py diff --git a/compute/compute/recipes/routes/create_kms_route.py b/compute/client_library/recipes/routes/create_kms_route.py similarity index 100% rename from compute/compute/recipes/routes/create_kms_route.py rename to compute/client_library/recipes/routes/create_kms_route.py diff --git a/compute/compute/recipes/routes/delete.py b/compute/client_library/recipes/routes/delete.py similarity index 100% rename from compute/compute/recipes/routes/delete.py rename to compute/client_library/recipes/routes/delete.py diff --git a/compute/compute/recipes/routes/list.py b/compute/client_library/recipes/routes/list.py similarity index 100% rename from compute/compute/recipes/routes/list.py rename to compute/client_library/recipes/routes/list.py diff --git a/compute/compute/recipes/snapshots/__init__.py b/compute/client_library/recipes/snapshots/__init__.py similarity index 100% rename from compute/compute/recipes/snapshots/__init__.py rename to compute/client_library/recipes/snapshots/__init__.py diff --git a/compute/compute/recipes/snapshots/create.py b/compute/client_library/recipes/snapshots/create.py similarity index 100% rename from compute/compute/recipes/snapshots/create.py rename to compute/client_library/recipes/snapshots/create.py diff --git a/compute/compute/recipes/snapshots/delete.py b/compute/client_library/recipes/snapshots/delete.py similarity index 100% rename from compute/compute/recipes/snapshots/delete.py rename to compute/client_library/recipes/snapshots/delete.py diff --git a/compute/compute/recipes/snapshots/delete_by_filter.py b/compute/client_library/recipes/snapshots/delete_by_filter.py similarity index 100% rename from compute/compute/recipes/snapshots/delete_by_filter.py rename to compute/client_library/recipes/snapshots/delete_by_filter.py diff --git a/compute/compute/recipes/snapshots/get.py b/compute/client_library/recipes/snapshots/get.py similarity index 100% rename from compute/compute/recipes/snapshots/get.py rename to compute/client_library/recipes/snapshots/get.py diff --git a/compute/compute/recipes/snapshots/list.py b/compute/client_library/recipes/snapshots/list.py similarity index 100% rename from compute/compute/recipes/snapshots/list.py rename to compute/client_library/recipes/snapshots/list.py diff --git a/compute/compute/recipes/usage_report/__init__.py b/compute/client_library/recipes/usage_report/__init__.py similarity index 100% rename from compute/compute/recipes/usage_report/__init__.py rename to compute/client_library/recipes/usage_report/__init__.py diff --git a/compute/compute/recipes/usage_report/usage_reports.py b/compute/client_library/recipes/usage_report/usage_reports.py similarity index 100% rename from compute/compute/recipes/usage_report/usage_reports.py rename to compute/client_library/recipes/usage_report/usage_reports.py diff --git a/compute/compute/requirements-test.txt b/compute/client_library/requirements-test.txt similarity index 100% rename from compute/compute/requirements-test.txt rename to compute/client_library/requirements-test.txt diff --git a/compute/compute/requirements.txt b/compute/client_library/requirements.txt similarity index 100% rename from compute/compute/requirements.txt rename to compute/client_library/requirements.txt diff --git a/compute/compute/sgs.py b/compute/client_library/sgs.py similarity index 100% rename from compute/compute/sgs.py rename to compute/client_library/sgs.py diff --git a/compute/compute/sgs_test_fixtures/ingredients/ingredient1.pytest b/compute/client_library/sgs_test_fixtures/ingredients/ingredient1.pytest similarity index 100% rename from compute/compute/sgs_test_fixtures/ingredients/ingredient1.pytest rename to compute/client_library/sgs_test_fixtures/ingredients/ingredient1.pytest diff --git a/compute/compute/sgs_test_fixtures/ingredients/ingredient2.pytest b/compute/client_library/sgs_test_fixtures/ingredients/ingredient2.pytest similarity index 100% rename from compute/compute/sgs_test_fixtures/ingredients/ingredient2.pytest rename to compute/client_library/sgs_test_fixtures/ingredients/ingredient2.pytest diff --git a/compute/compute/sgs_test_fixtures/output/experimental_recipe.pytest b/compute/client_library/sgs_test_fixtures/output/experimental_recipe.pytest similarity index 100% rename from compute/compute/sgs_test_fixtures/output/experimental_recipe.pytest rename to compute/client_library/sgs_test_fixtures/output/experimental_recipe.pytest diff --git a/compute/compute/sgs_test_fixtures/recipes/experimental_recipe.pytest b/compute/client_library/sgs_test_fixtures/recipes/experimental_recipe.pytest similarity index 100% rename from compute/compute/sgs_test_fixtures/recipes/experimental_recipe.pytest rename to compute/client_library/sgs_test_fixtures/recipes/experimental_recipe.pytest diff --git a/compute/compute/snippets/README.md b/compute/client_library/snippets/README.md similarity index 100% rename from compute/compute/snippets/README.md rename to compute/client_library/snippets/README.md diff --git a/compute/compute/snippets/__init__.py b/compute/client_library/snippets/__init__.py similarity index 100% rename from compute/compute/snippets/__init__.py rename to compute/client_library/snippets/__init__.py diff --git a/compute/compute/snippets/disks/__init__.py b/compute/client_library/snippets/disks/__init__.py similarity index 100% rename from compute/compute/snippets/disks/__init__.py rename to compute/client_library/snippets/disks/__init__.py diff --git a/compute/compute/snippets/disks/autodelete_change.py b/compute/client_library/snippets/disks/autodelete_change.py similarity index 100% rename from compute/compute/snippets/disks/autodelete_change.py rename to compute/client_library/snippets/disks/autodelete_change.py diff --git a/compute/compute/snippets/disks/clone_encrypted_disk.py b/compute/client_library/snippets/disks/clone_encrypted_disk.py similarity index 100% rename from compute/compute/snippets/disks/clone_encrypted_disk.py rename to compute/client_library/snippets/disks/clone_encrypted_disk.py diff --git a/compute/compute/snippets/disks/clone_encrypted_disk_managed_key.py b/compute/client_library/snippets/disks/clone_encrypted_disk_managed_key.py similarity index 100% rename from compute/compute/snippets/disks/clone_encrypted_disk_managed_key.py rename to compute/client_library/snippets/disks/clone_encrypted_disk_managed_key.py diff --git a/compute/compute/snippets/disks/create_empty_disk.py b/compute/client_library/snippets/disks/create_empty_disk.py similarity index 100% rename from compute/compute/snippets/disks/create_empty_disk.py rename to compute/client_library/snippets/disks/create_empty_disk.py diff --git a/compute/compute/snippets/disks/create_from_image.py b/compute/client_library/snippets/disks/create_from_image.py similarity index 100% rename from compute/compute/snippets/disks/create_from_image.py rename to compute/client_library/snippets/disks/create_from_image.py diff --git a/compute/compute/snippets/disks/create_from_snapshot.py b/compute/client_library/snippets/disks/create_from_snapshot.py similarity index 100% rename from compute/compute/snippets/disks/create_from_snapshot.py rename to compute/client_library/snippets/disks/create_from_snapshot.py diff --git a/compute/compute/snippets/disks/create_from_source.py b/compute/client_library/snippets/disks/create_from_source.py similarity index 100% rename from compute/compute/snippets/disks/create_from_source.py rename to compute/client_library/snippets/disks/create_from_source.py diff --git a/compute/compute/snippets/disks/create_kms_encrypted_disk.py b/compute/client_library/snippets/disks/create_kms_encrypted_disk.py similarity index 100% rename from compute/compute/snippets/disks/create_kms_encrypted_disk.py rename to compute/client_library/snippets/disks/create_kms_encrypted_disk.py diff --git a/compute/compute/snippets/disks/delete.py b/compute/client_library/snippets/disks/delete.py similarity index 100% rename from compute/compute/snippets/disks/delete.py rename to compute/client_library/snippets/disks/delete.py diff --git a/compute/compute/snippets/disks/list.py b/compute/client_library/snippets/disks/list.py similarity index 100% rename from compute/compute/snippets/disks/list.py rename to compute/client_library/snippets/disks/list.py diff --git a/compute/compute/snippets/disks/regional_create_from_source.py b/compute/client_library/snippets/disks/regional_create_from_source.py similarity index 100% rename from compute/compute/snippets/disks/regional_create_from_source.py rename to compute/client_library/snippets/disks/regional_create_from_source.py diff --git a/compute/compute/snippets/disks/regional_delete.py b/compute/client_library/snippets/disks/regional_delete.py similarity index 100% rename from compute/compute/snippets/disks/regional_delete.py rename to compute/client_library/snippets/disks/regional_delete.py diff --git a/compute/compute/snippets/firewall/__init__.py b/compute/client_library/snippets/firewall/__init__.py similarity index 100% rename from compute/compute/snippets/firewall/__init__.py rename to compute/client_library/snippets/firewall/__init__.py diff --git a/compute/compute/snippets/firewall/create.py b/compute/client_library/snippets/firewall/create.py similarity index 100% rename from compute/compute/snippets/firewall/create.py rename to compute/client_library/snippets/firewall/create.py diff --git a/compute/compute/snippets/firewall/delete.py b/compute/client_library/snippets/firewall/delete.py similarity index 100% rename from compute/compute/snippets/firewall/delete.py rename to compute/client_library/snippets/firewall/delete.py diff --git a/compute/compute/snippets/firewall/list.py b/compute/client_library/snippets/firewall/list.py similarity index 100% rename from compute/compute/snippets/firewall/list.py rename to compute/client_library/snippets/firewall/list.py diff --git a/compute/compute/snippets/firewall/main.py b/compute/client_library/snippets/firewall/main.py similarity index 100% rename from compute/compute/snippets/firewall/main.py rename to compute/client_library/snippets/firewall/main.py diff --git a/compute/compute/snippets/firewall/patch.py b/compute/client_library/snippets/firewall/patch.py similarity index 100% rename from compute/compute/snippets/firewall/patch.py rename to compute/client_library/snippets/firewall/patch.py diff --git a/compute/compute/snippets/firewall/windows_kms.py b/compute/client_library/snippets/firewall/windows_kms.py similarity index 100% rename from compute/compute/snippets/firewall/windows_kms.py rename to compute/client_library/snippets/firewall/windows_kms.py diff --git a/compute/compute/snippets/images/__init__.py b/compute/client_library/snippets/images/__init__.py similarity index 100% rename from compute/compute/snippets/images/__init__.py rename to compute/client_library/snippets/images/__init__.py diff --git a/compute/compute/snippets/images/create.py b/compute/client_library/snippets/images/create.py similarity index 100% rename from compute/compute/snippets/images/create.py rename to compute/client_library/snippets/images/create.py diff --git a/compute/compute/snippets/images/create_from_image.py b/compute/client_library/snippets/images/create_from_image.py similarity index 100% rename from compute/compute/snippets/images/create_from_image.py rename to compute/client_library/snippets/images/create_from_image.py diff --git a/compute/compute/snippets/images/create_from_snapshot.py b/compute/client_library/snippets/images/create_from_snapshot.py similarity index 100% rename from compute/compute/snippets/images/create_from_snapshot.py rename to compute/client_library/snippets/images/create_from_snapshot.py diff --git a/compute/compute/snippets/images/delete.py b/compute/client_library/snippets/images/delete.py similarity index 100% rename from compute/compute/snippets/images/delete.py rename to compute/client_library/snippets/images/delete.py diff --git a/compute/compute/snippets/images/get.py b/compute/client_library/snippets/images/get.py similarity index 100% rename from compute/compute/snippets/images/get.py rename to compute/client_library/snippets/images/get.py diff --git a/compute/compute/snippets/images/list.py b/compute/client_library/snippets/images/list.py similarity index 100% rename from compute/compute/snippets/images/list.py rename to compute/client_library/snippets/images/list.py diff --git a/compute/compute/snippets/images/pagination.py b/compute/client_library/snippets/images/pagination.py similarity index 100% rename from compute/compute/snippets/images/pagination.py rename to compute/client_library/snippets/images/pagination.py diff --git a/compute/compute/snippets/images/set_deprecation_status.py b/compute/client_library/snippets/images/set_deprecation_status.py similarity index 100% rename from compute/compute/snippets/images/set_deprecation_status.py rename to compute/client_library/snippets/images/set_deprecation_status.py diff --git a/compute/compute/snippets/instance_templates/__init__.py b/compute/client_library/snippets/instance_templates/__init__.py similarity index 100% rename from compute/compute/snippets/instance_templates/__init__.py rename to compute/client_library/snippets/instance_templates/__init__.py diff --git a/compute/compute/snippets/instance_templates/create.py b/compute/client_library/snippets/instance_templates/create.py similarity index 100% rename from compute/compute/snippets/instance_templates/create.py rename to compute/client_library/snippets/instance_templates/create.py diff --git a/compute/compute/snippets/instance_templates/create_from_instance.py b/compute/client_library/snippets/instance_templates/create_from_instance.py similarity index 100% rename from compute/compute/snippets/instance_templates/create_from_instance.py rename to compute/client_library/snippets/instance_templates/create_from_instance.py diff --git a/compute/compute/snippets/instance_templates/create_with_subnet.py b/compute/client_library/snippets/instance_templates/create_with_subnet.py similarity index 100% rename from compute/compute/snippets/instance_templates/create_with_subnet.py rename to compute/client_library/snippets/instance_templates/create_with_subnet.py diff --git a/compute/compute/snippets/instance_templates/delete.py b/compute/client_library/snippets/instance_templates/delete.py similarity index 100% rename from compute/compute/snippets/instance_templates/delete.py rename to compute/client_library/snippets/instance_templates/delete.py diff --git a/compute/compute/snippets/instance_templates/get.py b/compute/client_library/snippets/instance_templates/get.py similarity index 100% rename from compute/compute/snippets/instance_templates/get.py rename to compute/client_library/snippets/instance_templates/get.py diff --git a/compute/compute/snippets/instance_templates/list.py b/compute/client_library/snippets/instance_templates/list.py similarity index 100% rename from compute/compute/snippets/instance_templates/list.py rename to compute/client_library/snippets/instance_templates/list.py diff --git a/compute/compute/snippets/instances/__init__.py b/compute/client_library/snippets/instances/__init__.py similarity index 100% rename from compute/compute/snippets/instances/__init__.py rename to compute/client_library/snippets/instances/__init__.py diff --git a/compute/compute/snippets/instances/bulk_insert.py b/compute/client_library/snippets/instances/bulk_insert.py similarity index 100% rename from compute/compute/snippets/instances/bulk_insert.py rename to compute/client_library/snippets/instances/bulk_insert.py diff --git a/compute/compute/snippets/instances/create.py b/compute/client_library/snippets/instances/create.py similarity index 100% rename from compute/compute/snippets/instances/create.py rename to compute/client_library/snippets/instances/create.py diff --git a/compute/compute/snippets/instances/create_start_instance/__init__.py b/compute/client_library/snippets/instances/create_start_instance/__init__.py similarity index 100% rename from compute/compute/snippets/instances/create_start_instance/__init__.py rename to compute/client_library/snippets/instances/create_start_instance/__init__.py diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py b/compute/client_library/snippets/instances/create_start_instance/create_from_custom_image.py similarity index 100% rename from compute/compute/snippets/instances/create_start_instance/create_from_custom_image.py rename to compute/client_library/snippets/instances/create_start_instance/create_from_custom_image.py diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_public_image.py b/compute/client_library/snippets/instances/create_start_instance/create_from_public_image.py similarity index 100% rename from compute/compute/snippets/instances/create_start_instance/create_from_public_image.py rename to compute/client_library/snippets/instances/create_start_instance/create_from_public_image.py diff --git a/compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py b/compute/client_library/snippets/instances/create_start_instance/create_from_snapshot.py similarity index 100% rename from compute/compute/snippets/instances/create_start_instance/create_from_snapshot.py rename to compute/client_library/snippets/instances/create_start_instance/create_from_snapshot.py diff --git a/compute/compute/snippets/instances/create_start_instance/create_windows_instance.py b/compute/client_library/snippets/instances/create_start_instance/create_windows_instance.py similarity index 100% rename from compute/compute/snippets/instances/create_start_instance/create_windows_instance.py rename to compute/client_library/snippets/instances/create_start_instance/create_windows_instance.py diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py b/compute/client_library/snippets/instances/create_start_instance/create_with_additional_disk.py similarity index 100% rename from compute/compute/snippets/instances/create_start_instance/create_with_additional_disk.py rename to compute/client_library/snippets/instances/create_start_instance/create_with_additional_disk.py diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py b/compute/client_library/snippets/instances/create_start_instance/create_with_existing_disks.py similarity index 100% rename from compute/compute/snippets/instances/create_start_instance/create_with_existing_disks.py rename to compute/client_library/snippets/instances/create_start_instance/create_with_existing_disks.py diff --git a/compute/compute/snippets/instances/create_start_instance/create_with_local_ssd.py b/compute/client_library/snippets/instances/create_start_instance/create_with_local_ssd.py similarity index 100% rename from compute/compute/snippets/instances/create_start_instance/create_with_local_ssd.py rename to compute/client_library/snippets/instances/create_start_instance/create_with_local_ssd.py diff --git a/compute/compute/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 similarity index 100% rename from compute/compute/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py rename to compute/client_library/snippets/instances/create_start_instance/create_with_snapshotted_data_disk.py diff --git a/compute/compute/snippets/instances/create_with_subnet.py b/compute/client_library/snippets/instances/create_with_subnet.py similarity index 100% rename from compute/compute/snippets/instances/create_with_subnet.py rename to compute/client_library/snippets/instances/create_with_subnet.py diff --git a/compute/compute/snippets/instances/custom_hostname/create.py b/compute/client_library/snippets/instances/custom_hostname/create.py similarity index 100% rename from compute/compute/snippets/instances/custom_hostname/create.py rename to compute/client_library/snippets/instances/custom_hostname/create.py diff --git a/compute/compute/snippets/instances/custom_hostname/get.py b/compute/client_library/snippets/instances/custom_hostname/get.py similarity index 100% rename from compute/compute/snippets/instances/custom_hostname/get.py rename to compute/client_library/snippets/instances/custom_hostname/get.py diff --git a/compute/compute/snippets/instances/custom_machine_types/__init__.py b/compute/client_library/snippets/instances/custom_machine_types/__init__.py similarity index 100% rename from compute/compute/snippets/instances/custom_machine_types/__init__.py rename to compute/client_library/snippets/instances/custom_machine_types/__init__.py diff --git a/compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py b/compute/client_library/snippets/instances/custom_machine_types/create_shared_with_helper.py similarity index 100% rename from compute/compute/snippets/instances/custom_machine_types/create_shared_with_helper.py rename to compute/client_library/snippets/instances/custom_machine_types/create_shared_with_helper.py diff --git a/compute/compute/snippets/instances/custom_machine_types/create_with_helper.py b/compute/client_library/snippets/instances/custom_machine_types/create_with_helper.py similarity index 100% rename from compute/compute/snippets/instances/custom_machine_types/create_with_helper.py rename to compute/client_library/snippets/instances/custom_machine_types/create_with_helper.py diff --git a/compute/compute/snippets/instances/custom_machine_types/create_without_helper.py b/compute/client_library/snippets/instances/custom_machine_types/create_without_helper.py similarity index 100% rename from compute/compute/snippets/instances/custom_machine_types/create_without_helper.py rename to compute/client_library/snippets/instances/custom_machine_types/create_without_helper.py diff --git a/compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py b/compute/client_library/snippets/instances/custom_machine_types/extra_mem_no_helper.py similarity index 100% rename from compute/compute/snippets/instances/custom_machine_types/extra_mem_no_helper.py rename to compute/client_library/snippets/instances/custom_machine_types/extra_mem_no_helper.py diff --git a/compute/compute/snippets/instances/custom_machine_types/helper_class.py b/compute/client_library/snippets/instances/custom_machine_types/helper_class.py similarity index 100% rename from compute/compute/snippets/instances/custom_machine_types/helper_class.py rename to compute/client_library/snippets/instances/custom_machine_types/helper_class.py diff --git a/compute/compute/snippets/instances/custom_machine_types/update_memory.py b/compute/client_library/snippets/instances/custom_machine_types/update_memory.py similarity index 100% rename from compute/compute/snippets/instances/custom_machine_types/update_memory.py rename to compute/client_library/snippets/instances/custom_machine_types/update_memory.py diff --git a/compute/compute/snippets/instances/delete.py b/compute/client_library/snippets/instances/delete.py similarity index 100% rename from compute/compute/snippets/instances/delete.py rename to compute/client_library/snippets/instances/delete.py diff --git a/compute/compute/snippets/instances/delete_protection/__init__.py b/compute/client_library/snippets/instances/delete_protection/__init__.py similarity index 100% rename from compute/compute/snippets/instances/delete_protection/__init__.py rename to compute/client_library/snippets/instances/delete_protection/__init__.py diff --git a/compute/compute/snippets/instances/delete_protection/create.py b/compute/client_library/snippets/instances/delete_protection/create.py similarity index 100% rename from compute/compute/snippets/instances/delete_protection/create.py rename to compute/client_library/snippets/instances/delete_protection/create.py diff --git a/compute/compute/snippets/instances/delete_protection/get.py b/compute/client_library/snippets/instances/delete_protection/get.py similarity index 100% rename from compute/compute/snippets/instances/delete_protection/get.py rename to compute/client_library/snippets/instances/delete_protection/get.py diff --git a/compute/compute/snippets/instances/delete_protection/set.py b/compute/client_library/snippets/instances/delete_protection/set.py similarity index 100% rename from compute/compute/snippets/instances/delete_protection/set.py rename to compute/client_library/snippets/instances/delete_protection/set.py diff --git a/compute/compute/snippets/instances/from_instance_template/__init__.py b/compute/client_library/snippets/instances/from_instance_template/__init__.py similarity index 100% rename from compute/compute/snippets/instances/from_instance_template/__init__.py rename to compute/client_library/snippets/instances/from_instance_template/__init__.py diff --git a/compute/compute/snippets/instances/from_instance_template/create_from_template.py b/compute/client_library/snippets/instances/from_instance_template/create_from_template.py similarity index 100% rename from compute/compute/snippets/instances/from_instance_template/create_from_template.py rename to compute/client_library/snippets/instances/from_instance_template/create_from_template.py diff --git a/compute/compute/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 similarity index 100% rename from compute/compute/snippets/instances/from_instance_template/create_from_template_with_overrides.py rename to compute/client_library/snippets/instances/from_instance_template/create_from_template_with_overrides.py diff --git a/compute/compute/snippets/instances/get.py b/compute/client_library/snippets/instances/get.py similarity index 100% rename from compute/compute/snippets/instances/get.py rename to compute/client_library/snippets/instances/get.py diff --git a/compute/compute/snippets/instances/list.py b/compute/client_library/snippets/instances/list.py similarity index 100% rename from compute/compute/snippets/instances/list.py rename to compute/client_library/snippets/instances/list.py diff --git a/compute/compute/snippets/instances/list_all.py b/compute/client_library/snippets/instances/list_all.py similarity index 100% rename from compute/compute/snippets/instances/list_all.py rename to compute/client_library/snippets/instances/list_all.py diff --git a/compute/compute/snippets/instances/preemptible/__init__.py b/compute/client_library/snippets/instances/preemptible/__init__.py similarity index 100% rename from compute/compute/snippets/instances/preemptible/__init__.py rename to compute/client_library/snippets/instances/preemptible/__init__.py diff --git a/compute/compute/snippets/instances/preemptible/create_preemptible.py b/compute/client_library/snippets/instances/preemptible/create_preemptible.py similarity index 100% rename from compute/compute/snippets/instances/preemptible/create_preemptible.py rename to compute/client_library/snippets/instances/preemptible/create_preemptible.py diff --git a/compute/compute/snippets/instances/preemptible/is_preemptible.py b/compute/client_library/snippets/instances/preemptible/is_preemptible.py similarity index 100% rename from compute/compute/snippets/instances/preemptible/is_preemptible.py rename to compute/client_library/snippets/instances/preemptible/is_preemptible.py diff --git a/compute/compute/snippets/instances/preemptible/preemption_history.py b/compute/client_library/snippets/instances/preemptible/preemption_history.py similarity index 100% rename from compute/compute/snippets/instances/preemptible/preemption_history.py rename to compute/client_library/snippets/instances/preemptible/preemption_history.py diff --git a/compute/compute/snippets/instances/reset.py b/compute/client_library/snippets/instances/reset.py similarity index 100% rename from compute/compute/snippets/instances/reset.py rename to compute/client_library/snippets/instances/reset.py diff --git a/compute/compute/snippets/instances/resume.py b/compute/client_library/snippets/instances/resume.py similarity index 100% rename from compute/compute/snippets/instances/resume.py rename to compute/client_library/snippets/instances/resume.py diff --git a/compute/compute/snippets/instances/spot/__init__.py b/compute/client_library/snippets/instances/spot/__init__.py similarity index 100% rename from compute/compute/snippets/instances/spot/__init__.py rename to compute/client_library/snippets/instances/spot/__init__.py diff --git a/compute/compute/snippets/instances/spot/create.py b/compute/client_library/snippets/instances/spot/create.py similarity index 100% rename from compute/compute/snippets/instances/spot/create.py rename to compute/client_library/snippets/instances/spot/create.py diff --git a/compute/compute/snippets/instances/spot/is_spot_vm.py b/compute/client_library/snippets/instances/spot/is_spot_vm.py similarity index 100% rename from compute/compute/snippets/instances/spot/is_spot_vm.py rename to compute/client_library/snippets/instances/spot/is_spot_vm.py diff --git a/compute/compute/snippets/instances/start.py b/compute/client_library/snippets/instances/start.py similarity index 100% rename from compute/compute/snippets/instances/start.py rename to compute/client_library/snippets/instances/start.py diff --git a/compute/compute/snippets/instances/start_encrypted.py b/compute/client_library/snippets/instances/start_encrypted.py similarity index 100% rename from compute/compute/snippets/instances/start_encrypted.py rename to compute/client_library/snippets/instances/start_encrypted.py diff --git a/compute/compute/snippets/instances/stop.py b/compute/client_library/snippets/instances/stop.py similarity index 100% rename from compute/compute/snippets/instances/stop.py rename to compute/client_library/snippets/instances/stop.py diff --git a/compute/compute/snippets/instances/suspend.py b/compute/client_library/snippets/instances/suspend.py similarity index 100% rename from compute/compute/snippets/instances/suspend.py rename to compute/client_library/snippets/instances/suspend.py diff --git a/compute/compute/snippets/operations/__init__.py b/compute/client_library/snippets/operations/__init__.py similarity index 100% rename from compute/compute/snippets/operations/__init__.py rename to compute/client_library/snippets/operations/__init__.py diff --git a/compute/compute/snippets/operations/operation_check.py b/compute/client_library/snippets/operations/operation_check.py similarity index 100% rename from compute/compute/snippets/operations/operation_check.py rename to compute/client_library/snippets/operations/operation_check.py diff --git a/compute/compute/snippets/routes/create.py b/compute/client_library/snippets/routes/create.py similarity index 100% rename from compute/compute/snippets/routes/create.py rename to compute/client_library/snippets/routes/create.py diff --git a/compute/compute/snippets/routes/create_kms_route.py b/compute/client_library/snippets/routes/create_kms_route.py similarity index 100% rename from compute/compute/snippets/routes/create_kms_route.py rename to compute/client_library/snippets/routes/create_kms_route.py diff --git a/compute/compute/snippets/routes/delete.py b/compute/client_library/snippets/routes/delete.py similarity index 100% rename from compute/compute/snippets/routes/delete.py rename to compute/client_library/snippets/routes/delete.py diff --git a/compute/compute/snippets/routes/list.py b/compute/client_library/snippets/routes/list.py similarity index 100% rename from compute/compute/snippets/routes/list.py rename to compute/client_library/snippets/routes/list.py diff --git a/compute/compute/snippets/snapshots/__init__.py b/compute/client_library/snippets/snapshots/__init__.py similarity index 100% rename from compute/compute/snippets/snapshots/__init__.py rename to compute/client_library/snippets/snapshots/__init__.py diff --git a/compute/compute/snippets/snapshots/create.py b/compute/client_library/snippets/snapshots/create.py similarity index 100% rename from compute/compute/snippets/snapshots/create.py rename to compute/client_library/snippets/snapshots/create.py diff --git a/compute/compute/snippets/snapshots/delete.py b/compute/client_library/snippets/snapshots/delete.py similarity index 100% rename from compute/compute/snippets/snapshots/delete.py rename to compute/client_library/snippets/snapshots/delete.py diff --git a/compute/compute/snippets/snapshots/delete_by_filter.py b/compute/client_library/snippets/snapshots/delete_by_filter.py similarity index 100% rename from compute/compute/snippets/snapshots/delete_by_filter.py rename to compute/client_library/snippets/snapshots/delete_by_filter.py diff --git a/compute/compute/snippets/snapshots/get.py b/compute/client_library/snippets/snapshots/get.py similarity index 100% rename from compute/compute/snippets/snapshots/get.py rename to compute/client_library/snippets/snapshots/get.py diff --git a/compute/compute/snippets/snapshots/list.py b/compute/client_library/snippets/snapshots/list.py similarity index 100% rename from compute/compute/snippets/snapshots/list.py rename to compute/client_library/snippets/snapshots/list.py diff --git a/compute/compute/snippets/tests/__init__.py b/compute/client_library/snippets/tests/__init__.py similarity index 100% rename from compute/compute/snippets/tests/__init__.py rename to compute/client_library/snippets/tests/__init__.py diff --git a/compute/compute/snippets/tests/test_bulk.py b/compute/client_library/snippets/tests/test_bulk.py similarity index 100% rename from compute/compute/snippets/tests/test_bulk.py rename to compute/client_library/snippets/tests/test_bulk.py diff --git a/compute/compute/snippets/tests/test_create_vm.py b/compute/client_library/snippets/tests/test_create_vm.py similarity index 100% rename from compute/compute/snippets/tests/test_create_vm.py rename to compute/client_library/snippets/tests/test_create_vm.py diff --git a/compute/compute/snippets/tests/test_custom_hostnames.py b/compute/client_library/snippets/tests/test_custom_hostnames.py similarity index 100% rename from compute/compute/snippets/tests/test_custom_hostnames.py rename to compute/client_library/snippets/tests/test_custom_hostnames.py diff --git a/compute/compute/snippets/tests/test_custom_types.py b/compute/client_library/snippets/tests/test_custom_types.py similarity index 100% rename from compute/compute/snippets/tests/test_custom_types.py rename to compute/client_library/snippets/tests/test_custom_types.py diff --git a/compute/compute/snippets/tests/test_default_values.py b/compute/client_library/snippets/tests/test_default_values.py similarity index 100% rename from compute/compute/snippets/tests/test_default_values.py rename to compute/client_library/snippets/tests/test_default_values.py diff --git a/compute/compute/snippets/tests/test_delete_protection.py b/compute/client_library/snippets/tests/test_delete_protection.py similarity index 100% rename from compute/compute/snippets/tests/test_delete_protection.py rename to compute/client_library/snippets/tests/test_delete_protection.py diff --git a/compute/compute/snippets/tests/test_disks.py b/compute/client_library/snippets/tests/test_disks.py similarity index 100% rename from compute/compute/snippets/tests/test_disks.py rename to compute/client_library/snippets/tests/test_disks.py diff --git a/compute/compute/snippets/tests/test_firewall.py b/compute/client_library/snippets/tests/test_firewall.py similarity index 100% rename from compute/compute/snippets/tests/test_firewall.py rename to compute/client_library/snippets/tests/test_firewall.py diff --git a/compute/compute/snippets/tests/test_images.py b/compute/client_library/snippets/tests/test_images.py similarity index 100% rename from compute/compute/snippets/tests/test_images.py rename to compute/client_library/snippets/tests/test_images.py diff --git a/compute/compute/snippets/tests/test_instance_from_template.py b/compute/client_library/snippets/tests/test_instance_from_template.py similarity index 100% rename from compute/compute/snippets/tests/test_instance_from_template.py rename to compute/client_library/snippets/tests/test_instance_from_template.py diff --git a/compute/compute/snippets/tests/test_instance_start_stop.py b/compute/client_library/snippets/tests/test_instance_start_stop.py similarity index 100% rename from compute/compute/snippets/tests/test_instance_start_stop.py rename to compute/client_library/snippets/tests/test_instance_start_stop.py diff --git a/compute/compute/snippets/tests/test_instance_suspend_resume.py b/compute/client_library/snippets/tests/test_instance_suspend_resume.py similarity index 100% rename from compute/compute/snippets/tests/test_instance_suspend_resume.py rename to compute/client_library/snippets/tests/test_instance_suspend_resume.py diff --git a/compute/compute/snippets/tests/test_pagination.py b/compute/client_library/snippets/tests/test_pagination.py similarity index 100% rename from compute/compute/snippets/tests/test_pagination.py rename to compute/client_library/snippets/tests/test_pagination.py diff --git a/compute/compute/snippets/tests/test_preemptible.py b/compute/client_library/snippets/tests/test_preemptible.py similarity index 100% rename from compute/compute/snippets/tests/test_preemptible.py rename to compute/client_library/snippets/tests/test_preemptible.py diff --git a/compute/compute/snippets/tests/test_request_id.py b/compute/client_library/snippets/tests/test_request_id.py similarity index 100% rename from compute/compute/snippets/tests/test_request_id.py rename to compute/client_library/snippets/tests/test_request_id.py diff --git a/compute/compute/snippets/tests/test_route.py b/compute/client_library/snippets/tests/test_route.py similarity index 100% rename from compute/compute/snippets/tests/test_route.py rename to compute/client_library/snippets/tests/test_route.py diff --git a/compute/compute/snippets/tests/test_snapshots.py b/compute/client_library/snippets/tests/test_snapshots.py similarity index 100% rename from compute/compute/snippets/tests/test_snapshots.py rename to compute/client_library/snippets/tests/test_snapshots.py diff --git a/compute/compute/snippets/tests/test_spot_vms.py b/compute/client_library/snippets/tests/test_spot_vms.py similarity index 100% rename from compute/compute/snippets/tests/test_spot_vms.py rename to compute/client_library/snippets/tests/test_spot_vms.py diff --git a/compute/compute/snippets/tests/test_templates.py b/compute/client_library/snippets/tests/test_templates.py similarity index 100% rename from compute/compute/snippets/tests/test_templates.py rename to compute/client_library/snippets/tests/test_templates.py diff --git a/compute/compute/snippets/usage_report/__init__.py b/compute/client_library/snippets/usage_report/__init__.py similarity index 100% rename from compute/compute/snippets/usage_report/__init__.py rename to compute/client_library/snippets/usage_report/__init__.py diff --git a/compute/compute/snippets/usage_report/usage_reports.py b/compute/client_library/snippets/usage_report/usage_reports.py similarity index 100% rename from compute/compute/snippets/usage_report/usage_reports.py rename to compute/client_library/snippets/usage_report/usage_reports.py diff --git a/compute/compute/test_sgs.py b/compute/client_library/test_sgs.py similarity index 100% rename from compute/compute/test_sgs.py rename to compute/client_library/test_sgs.py