diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b0219d1739bc..f1ee8750fc91 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -28,6 +28,7 @@ /codelabs/**/* @GoogleCloudPlatform/python-samples-reviewers /composer/**/* @leahecole @rachael-ds @rafalbiegacz @GoogleCloudPlatform/python-samples-reviewers /compute/**/* @m-strzelczyk @GoogleCloudPlatform/python-samples-reviewers +/container/**/* @GoogleCloudPlatform/dee-platform-ops @GoogleCloudPlatform/python-samples-reviewers /data-science-onramp/ @leahecole @bradmiro @GoogleCloudPlatform/python-samples-reviewers /dataflow/**/* @davidcavazos @GoogleCloudPlatform/python-samples-reviewers /datastore/**/* @GoogleCloudPlatform/cloud-native-db-dpes @GoogleCloudPlatform/python-samples-reviewers diff --git a/.github/blunderbuss.yml b/.github/blunderbuss.yml index d672483061d3..bc1059f6faa8 100644 --- a/.github/blunderbuss.yml +++ b/.github/blunderbuss.yml @@ -47,6 +47,10 @@ assign_issues_by: - 'api: compute' to: - m-strzelczyk +- labels: + - 'api: container' + to: + - GoogleCloudPlatform/dee-platform-ops - labels: - 'api: datascienceonramp' to: diff --git a/.kokoro/tests/run_tests.sh b/.kokoro/tests/run_tests.sh index 41cbcfa11325..f7397b13444f 100755 --- a/.kokoro/tests/run_tests.sh +++ b/.kokoro/tests/run_tests.sh @@ -159,7 +159,7 @@ test_prog="${PROJECT_ROOT}/.kokoro/tests/run_single_test.sh" btlr_args=( "run" - "--max-cmd-duration=30m" + "--max-cmd-duration=60m" "**/requirements.txt" ) diff --git a/container/AUTHORING_GUIDE.md b/container/AUTHORING_GUIDE.md new file mode 100644 index 000000000000..8249522ffc2d --- /dev/null +++ b/container/AUTHORING_GUIDE.md @@ -0,0 +1 @@ +See https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/AUTHORING_GUIDE.md \ No newline at end of file diff --git a/container/CONTRIBUTING.md b/container/CONTRIBUTING.md new file mode 100644 index 000000000000..f5fe2e6baf13 --- /dev/null +++ b/container/CONTRIBUTING.md @@ -0,0 +1 @@ +See https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/CONTRIBUTING.md \ No newline at end of file diff --git a/container/snippets/README.md b/container/snippets/README.md new file mode 100644 index 000000000000..a1d0691fd8c2 --- /dev/null +++ b/container/snippets/README.md @@ -0,0 +1,45 @@ +# Samples + +All the samples are self contained unless they are placed inside their own folders. The samples use [Application Default Credentails (ADC)](https://cloud.google.com/docs/authentication/production#automatically) to authenticate with GCP. So make sure ADC is setup correctly _(i.e. `GOOGLE_APPLICATION_CREDENTIALS` environment variable is set)_ before running the samples. Some sample might require additional python modules to be installed. + +You can run samples as follows: + +```python +python ... +``` + +You can run the following command to find the usage and arguments for the samples: + +```python +python -h +``` +```bash +# example +python quickstart.py -h + +usage: quickstart.py [-h] project_id zone + +positional arguments: + project_id Google Cloud project ID + zone GKE Cluster zone + +optional arguments: + -h, --help show this help message and exit +``` + +### Quickstart sample +- [**quickstart.py**](quickstart.py): A simple example to list the GKE clusters in a given GCP project and zone. The sample uses the [`list_clusters()`](https://cloud.google.com/python/docs/reference/container/latest/google.cloud.container_v1.services.cluster_manager.ClusterManagerClient#google_cloud_container_v1_services_cluster_manager_ClusterManagerClient_list_clusters) API to fetch the list of cluster. + + +### Long running operation sample + +The following samples are examples of operations that take a while to complete. +For example _creating a cluster_ in GKE can take a while to set up the cluster +nodes, networking and configuring Kubernetes. Thus, calls to such long running +APIs return an object of type [`Operation`](https://cloud.google.com/python/docs/reference/container/latest/google.cloud.container_v1.types.Operation). We can +then use the id of the returned operation to **poll** the [`get_operation()`](https://cloud.google.com/python/docs/reference/container/latest/google.cloud.container_v1.services.cluster_manager.ClusterManagerClient#google_cloud_container_v1_services_cluster_manager_ClusterManagerClient_get_operation) API to check for it's status. You can see the +different statuses it can be in, in [this proto definition](https://github.com/googleapis/googleapis/blob/master/google/container/v1/cluster_service.proto#L1763-L1778). + +- [**create_cluster.py**](create_cluster.py): An example of creating a GKE cluster _(with mostly the defaults)_. This example shows how to handle responses of type [`Operation`](https://cloud.google.com/python/docs/reference/container/latest/google.cloud.container_v1.types.Operation) that reperesents a long running operation. The example uses the python module [`backoff`](https://github.com/litl/backoff) to handle a graceful exponential backoff retry mechanism to check if the `Operation` has completed. + +- [**delete_cluster.py**](delete_cluster.py): An example of deleting a GKE cluster. This example shows how to handle responses of type [`Operation`](https://cloud.google.com/python/docs/reference/container/latest/google.cloud.container_v1.types.Operation) that reperesents a long running operation. \ No newline at end of file diff --git a/container/snippets/create_cluster.py b/container/snippets/create_cluster.py new file mode 100644 index 000000000000..949c6f6e2afa --- /dev/null +++ b/container/snippets/create_cluster.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 gke_create_cluster] +import argparse +import sys +from typing import Dict + +import backoff +from google.cloud import container_v1 + + +def on_success(details: Dict[str, str]) -> None: + """ + A handler function to pass into the retry backoff algorithm as the function + to be executed upon a successful attempt. + + Read the `Event handlers` section of the backoff python module at: + https://pypi.org/project/backoff/ + """ + print("Successfully created cluster after {elapsed:0.1f} seconds".format(**details)) + + +def on_failure(details: Dict[str, str]) -> None: + """ + A handler function to pass into the retry backoff algorithm as the function + to be executed upon a failed attempt. + + Read the `Event handlers` section of the backoff python module at: + https://pypi.org/project/backoff/ + """ + print("Backing off {wait:0.1f} seconds after {tries} tries".format(**details)) + + +@backoff.on_predicate( + # the backoff algorithm to use. we use exponential backoff here + backoff.expo, + # the test function on the return value to determine if a retry is necessary + lambda x: x != container_v1.Operation.Status.DONE, + # maximum number of times to retry before giving up + max_tries=20, + # function to execute upon a failure and when a retry a scheduled + on_backoff=on_failure, + # function to execute upon a successful attempt and no more retries needed + on_success=on_success, +) +def poll_for_op_status( + client: container_v1.ClusterManagerClient, op_id: str +) -> container_v1.Operation.Status: + """ + This function calls the Operation API in GCP with the given operation id. It + serves as a simple retry function that fetches the operation and returns + it's status. + + We use the 'backoff' python module to provide us the implementation of the + backoff & retry strategy. The function is annotated with the `backoff` + python module to schedule this function based on a reasonable backoff + algorithm. + """ + + op = client.get_operation({"name": op_id}) + return op.status + + +def create_cluster(project_id: str, location: str, cluster_name: str) -> None: + """Create a new GKE cluster in the given GCP Project and Zone""" + # Initialize the Cluster management client. + client = container_v1.ClusterManagerClient() + # Create a fully qualified location identifier of form `projects/{project_id}/location/{zone}'. + cluster_location = client.common_location_path(project_id, location) + cluster_def = { + "name": cluster_name, + "initial_node_count": 2, + "node_config": {"machine_type": "e2-standard-2"}, + } + # Create the request object with the location identifier. + request = {"parent": cluster_location, "cluster": cluster_def} + create_response = client.create_cluster(request) + op_identifier = f"{cluster_location}/operations/{create_response.name}" + # poll for the operation status and schedule a retry until the cluster is created + poll_for_op_status(client, op_identifier) + + +# [END gke_create_cluster] + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("project_id", help="Google Cloud project ID") + parser.add_argument("zone", help="GKE Cluster zone") + parser.add_argument("cluster_name", help="Name to be given to the GKE Cluster") + args = parser.parse_args() + + if len(sys.argv) != 4: + parser.print_usage() + sys.exit(1) + + create_cluster(args.project_id, args.zone, args.cluster_name) diff --git a/container/snippets/create_cluster_test.py b/container/snippets/create_cluster_test.py new file mode 100644 index 000000000000..75a281c96015 --- /dev/null +++ b/container/snippets/create_cluster_test.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 os +import uuid + +import backoff +from google.cloud import container_v1 as gke +import pytest + +import create_cluster as gke_create + +PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"] +ZONE = "us-central1-b" +CLUSTER_NAME = f"py-container-repo-test-{uuid.uuid4().hex[:10]}" + + +@pytest.fixture(autouse=True) +def setup_and_tear_down() -> None: + + # nohing to setup here + + # run the tests here + yield + + try: + # delete the cluster + client = gke.ClusterManagerClient() + cluster_location = client.common_location_path(PROJECT_ID, ZONE) + cluster_name = f"{cluster_location}/clusters/{CLUSTER_NAME}" + op = client.delete_cluster({"name": cluster_name}) + op_id = f"{cluster_location}/operations/{op.name}" + + finally: + # schedule a retry to ensure the cluster is deleted + @backoff.on_predicate( + backoff.expo, lambda x: x != gke.Operation.Status.DONE, max_tries=20 + ) + def wait_for_delete() -> gke.Operation.Status: + return client.get_operation({"name": op_id}).status + + wait_for_delete() + + +def test_create_clusters(capsys: object) -> None: + gke_create.create_cluster(PROJECT_ID, ZONE, CLUSTER_NAME) + out, _ = capsys.readouterr() + + assert "Backing off " in out + assert "Successfully created cluster after" in out + + client = gke.ClusterManagerClient() + cluster_location = client.common_location_path(PROJECT_ID, ZONE) + list_response = client.list_clusters({"parent": cluster_location}) + + list_of_clusters = [] + for cluster in list_response.clusters: + list_of_clusters.append(cluster.name) + + assert CLUSTER_NAME in list_of_clusters diff --git a/container/snippets/delete_cluster.py b/container/snippets/delete_cluster.py new file mode 100644 index 000000000000..405de9788060 --- /dev/null +++ b/container/snippets/delete_cluster.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 gke_delete_cluster] +import argparse +import sys +from typing import Dict + +import backoff +from google.cloud import container_v1 + + +def on_success(details: Dict[str, str]) -> None: + """ + A handler function to pass into the retry backoff algorithm as the function + to be executed upon a successful attempt. + + Read the `Event handlers` section of the backoff python module at: + https://pypi.org/project/backoff/ + """ + print("Successfully deleted cluster after {elapsed:0.1f} seconds".format(**details)) + + +def on_failure(details: Dict[str, str]) -> None: + """ + A handler function to pass into the retry backoff algorithm as the function + to be executed upon a failed attempt. + + Read the `Event handlers` section of the backoff python module at: + https://pypi.org/project/backoff/ + """ + print("Backing off {wait:0.1f} seconds after {tries} tries".format(**details)) + + +@backoff.on_predicate( + # the backoff algorithm to use. we use exponential backoff here + backoff.expo, + # the test function on the return value to determine if a retry is necessary + lambda x: x != container_v1.Operation.Status.DONE, + # maximum number of times to retry before giving up + max_tries=20, + # function to execute upon a failure and when a retry is scheduled + on_backoff=on_failure, + # function to execute upon a successful attempt and no more retries needed + on_success=on_success, +) +def poll_for_op_status( + client: container_v1.ClusterManagerClient, op_id: str +) -> container_v1.Operation.Status: + """ + A simple retry function that fetches the operation and returns it's status. + + The function is annotated with the `backoff` python module to schedule this + function based on a reasonable backoff algorithm + """ + + op = client.get_operation({"name": op_id}) + return op.status + + +def delete_cluster(project_id: str, location: str, cluster_name: str) -> None: + """Delete an existing GKE cluster in the given GCP Project and Zone""" + + # Initialize the Cluster management client. + client = container_v1.ClusterManagerClient() + # Create a fully qualified location identifier of form `projects/{project_id}/location/{zone}'. + cluster_location = client.common_location_path(project_id, location) + cluster_name = f"{cluster_location}/clusters/{cluster_name}" + # Create the request object with the location identifier. + request = {"name": cluster_name} + delete_response = client.delete_cluster(request) + op_identifier = f"{cluster_location}/operations/{delete_response.name}" + # poll for the operation status until the cluster is deleted + poll_for_op_status(client, op_identifier) + + +# [END gke_delete_cluster] + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("project_id", help="Google Cloud project ID") + parser.add_argument("zone", help="GKE Cluster zone") + parser.add_argument("cluster_name", help="Name to be given to the GKE Cluster") + args = parser.parse_args() + + if len(sys.argv) != 4: + parser.print_usage() + sys.exit(1) + + delete_cluster(args.project_id, args.zone, args.cluster_name) diff --git a/container/snippets/delete_cluster_test.py b/container/snippets/delete_cluster_test.py new file mode 100644 index 000000000000..5e187d84de1a --- /dev/null +++ b/container/snippets/delete_cluster_test.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 os +import uuid + +import backoff +from google.api_core import exceptions as googleEx +from google.cloud import container_v1 as gke +import pytest + +import delete_cluster as gke_delete + +PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"] +ZONE = "us-central1-b" +CLUSTER_NAME = f"py-container-repo-test-{uuid.uuid4().hex[:10]}" + + +@pytest.fixture(autouse=True) +def setup_and_tear_down() -> None: + + # create a cluster to be deleted + client = gke.ClusterManagerClient() + cluster_location = client.common_location_path(PROJECT_ID, ZONE) + cluster_def = { + "name": CLUSTER_NAME, + "initial_node_count": 2, + "node_config": {"machine_type": "e2-standard-2"}, + } + op = client.create_cluster({"parent": cluster_location, "cluster": cluster_def}) + op_id = f"{cluster_location}/operations/{op.name}" + + # schedule a retry to ensure the cluster is created + @backoff.on_predicate( + backoff.expo, lambda x: x != gke.Operation.Status.DONE, max_tries=20 + ) + def wait_for_create() -> gke.Operation.Status: + return client.get_operation({"name": op_id}).status + + wait_for_create() + + # run the tests here + yield + + # delete the cluster in case the test itself failed + client = gke.ClusterManagerClient() + cluster_location = client.common_location_path(PROJECT_ID, ZONE) + cluster_name = f"{cluster_location}/clusters/{CLUSTER_NAME}" + + try: + op = client.delete_cluster({"name": cluster_name}) + op_id = f"{cluster_location}/operations/{op.name}" + + # schedule a retry to ensure the cluster is deleted + @backoff.on_predicate( + backoff.expo, lambda x: x != gke.Operation.Status.DONE, max_tries=20 + ) + def wait_for_delete() -> gke.Operation.Status: + return client.get_operation({"name": op_id}).status + + wait_for_delete() + except googleEx.NotFound: + # if the delete test passed then this is bound to happen + pass + + +def test_delete_clusters(capsys: object) -> None: + gke_delete.delete_cluster(PROJECT_ID, ZONE, CLUSTER_NAME) + out, _ = capsys.readouterr() + + assert "Backing off " in out + assert "Successfully deleted cluster after" in out + + client = gke.ClusterManagerClient() + cluster_location = client.common_location_path(PROJECT_ID, ZONE) + list_response = client.list_clusters({"parent": cluster_location}) + + list_of_clusters = [] + for cluster in list_response.clusters: + list_of_clusters.append(cluster.name) + + assert CLUSTER_NAME not in list_of_clusters diff --git a/container/snippets/noxfile_config.py b/container/snippets/noxfile_config.py new file mode 100644 index 000000000000..16f326e5c6d2 --- /dev/null +++ b/container/snippets/noxfile_config.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. + +# Default TEST_CONFIG_OVERRIDE for python repos. + +# You can copy this file into your directory, then it will be imported from +# the noxfile.py. + +# The source of truth: +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py + +TEST_CONFIG_OVERRIDE = { + # 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": True, + # 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": {}, +} diff --git a/container/snippets/quickstart.py b/container/snippets/quickstart.py new file mode 100644 index 000000000000..3299a9a1e669 --- /dev/null +++ b/container/snippets/quickstart.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 gke_list_cluster] +import argparse +import sys + +from google.cloud import container_v1 + + +def list_clusters(project_id: str, location: str) -> None: + """List all the GKE clusters in the given GCP Project and Zone""" + + # Initialize the Cluster management client. + client = container_v1.ClusterManagerClient() + # Create a fully qualified location identifier of form `projects/{project_id}/location/{zone}'. + cluster_location = client.common_location_path(project_id, location) + # Create the request object with the location identifier. + request = {"parent": cluster_location} + list_response = client.list_clusters(request) + + print( + f"There were {len(list_response.clusters)} clusters in {location} for project {project_id}." + ) + for cluster in list_response.clusters: + print(f"- {cluster.name}") + + +# [END gke_list_cluster] + +if __name__ == "__main__": + + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("project_id", help="Google Cloud project ID") + parser.add_argument("zone", help="GKE Cluster zone") + args = parser.parse_args() + + if len(sys.argv) != 3: + parser.print_usage() + sys.exit(1) + + list_clusters(args.project_id, args.zone) diff --git a/container/snippets/quickstart_test.py b/container/snippets/quickstart_test.py new file mode 100644 index 000000000000..7904fa5535c6 --- /dev/null +++ b/container/snippets/quickstart_test.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 os + +import quickstart as gke_list + +PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"] +ZONE = "us-central1-b" + + +def test_list_clusters(capsys: object) -> None: + output_prefix = "There were " + output_suffix = f" clusters in {ZONE} for project {PROJECT_ID}." + + gke_list.list_clusters(PROJECT_ID, ZONE) + out, _ = capsys.readouterr() + + """ + Typical output looks as follows: + + There were 3 clusters in us-central1-b for project test-project. + - cluster1 + - cluster2 + - cluster3 + + Split array by '\n' + [ + "There were 3 clusters in us-central1-b for project test-project.", + "- cluster1", + "- cluster2", + "- cluster3", + "", + ] + """ + out_lines = out.split("\n") + first_line = out_lines[0] + first_line = first_line.replace(output_prefix, "") + first_line = first_line.replace(output_suffix, "") + cluster_count = int(first_line) # get the cluster count in the first line + + assert output_suffix in out + assert cluster_count == len(out_lines) - 2 diff --git a/container/snippets/requirement-test.txt b/container/snippets/requirement-test.txt new file mode 100644 index 000000000000..52a772517632 --- /dev/null +++ b/container/snippets/requirement-test.txt @@ -0,0 +1,17 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +backoff==1.11.1 +pytest==7.0.0 +google-api-core=2.5.0 diff --git a/container/snippets/requirements.txt b/container/snippets/requirements.txt new file mode 100644 index 000000000000..801bc8b41d87 --- /dev/null +++ b/container/snippets/requirements.txt @@ -0,0 +1,3 @@ +google-cloud-container==2.13.0 +backoff==2.2.1 +pytest==7.2.0 \ No newline at end of file