Skip to content
This repository has been archived by the owner on Dec 31, 2023. It is now read-only.

docs(samples): Adding template samples #136

Merged
merged 20 commits into from Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4e46538
chore(docs): Instance template creation samples
m-strzelczyk Oct 18, 2021
0486123
Merge branch 'main' into samples-templates
m-strzelczyk Oct 19, 2021
35be7ec
chore(docs): Adding docstrings.
m-strzelczyk Oct 19, 2021
d2b43a1
Merge branch 'main' into samples-templates
m-strzelczyk Oct 19, 2021
d027304
Merge branch 'main' into samples-templates
m-strzelczyk Oct 19, 2021
ee4d8ae
chore(docs): Applying suggested changes.
m-strzelczyk Oct 21, 2021
41512b9
chore(docs): Trying to fix test errors.
m-strzelczyk Oct 21, 2021
758069e
Merge branch 'main' into samples-templates
m-strzelczyk Nov 3, 2021
83c1a1b
Merge branch 'main' into samples-templates
parthea Nov 4, 2021
3896df2
Merge branch 'main' into samples-templates
m-strzelczyk Nov 8, 2021
a41ee95
Apply suggestions from code review
m-strzelczyk Nov 8, 2021
deffb61
chore(docs): Applying changes from review.
m-strzelczyk Nov 8, 2021
7301bbd
Merge branch 'samples-templates' of github.com:googleapis/python-comp…
m-strzelczyk Nov 8, 2021
fe3d984
chore(docs): Fixing region tag.
m-strzelczyk Nov 8, 2021
5d67138
Merge branch 'main' into samples-templates
m-strzelczyk Nov 9, 2021
80bb1d8
chore(samples): Updating tests according to review.
m-strzelczyk Nov 15, 2021
889b4aa
Merge branch 'main' into samples-templates
m-strzelczyk Nov 15, 2021
c6993cf
chore(samples): Fixing the fixture import for tests to work.
m-strzelczyk Nov 15, 2021
702ea0e
Merge branch 'main' into samples-templates
parthea Nov 16, 2021
1e809cc
Merge branch 'main' into samples-templates
m-strzelczyk Nov 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
201 changes: 201 additions & 0 deletions samples/snippets/sample_templates.py
@@ -0,0 +1,201 @@
# 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

from google.cloud import compute_v1
m-strzelczyk marked this conversation as resolved.
Show resolved Hide resolved


def get_instance_template(project_id: str, template_name: str) -> compute_v1.InstanceTemplate:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally the preferred style for newly written samples is one sample per file, as it allows for imports to be included in the region tags: https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/AUTHORING_GUIDE.md#region-tags

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the guide you linked doesn't really state that there should be 1:1 ratio between files and samples and I've been doing multiple samples per file for some time now in this repo. See for example vm creation samples. This way imports are included in the samples just fine.

I've read the internal "Code Snippets 201" guide and figured that this is the best way to group the samples. I plan to reorganize the samples soon, to introduce more structure with folders for example. If there are reasons to introduce 1 sample = 1 file division, I'd like to know it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is essentially enforcement/ease of use. With your samples, their is only one import so it's very complicated. However if a new import would be added for whatever reason, it should only apply to the sample using it, which becomes more complicated. Same with when an import needs to be removed - if it's no longer using it in the sample (but is in a different sample in the same file), flake8 can't catch that it's only being used in some region tags but not all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I see your point. I will reorganize those samples soon, as we were planning to do a bigger refactoring of how we keep the samples for the Compute documentation in multiple languages.

Could you just suggest what would be the best course of action, when there are rather big pieces of code repeating in multiple samples like in the vm creation samples? There, the create_with_disks method is used by 6 different samples. Can a region snap multiple files? If not, I will have to replicate this code multiple times...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on how you arrange the samples on the page. If there really is a certain amount of boiler plate, you could create a different region tag for that section and have a different region tag that references. However, it's probably feasible to just integrate it as part of the main samples.

"""
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 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)


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.
network_interface.access_configs = [compute_v1.AccessConfig()]
network_interface.access_configs[0].name = "External NAT"
network_interface.access_configs[0].type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT
network_interface.access_configs[0].network_tier = (
compute_v1.AccessConfig.NetworkTier.PREMIUM
)
m-strzelczyk marked this conversation as resolved.
Show resolved Hide resolved

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)


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.
m-strzelczyk marked this conversation as resolved.
Show resolved Hide resolved
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)


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)


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
3 changes: 2 additions & 1 deletion samples/snippets/test_sample_start_stop.py
Expand Up @@ -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]

Expand All @@ -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()
Expand Down
98 changes: 98 additions & 0 deletions samples/snippets/test_sample_templates.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 sample_templates import (
create_template,
create_template_from_instance,
create_template_with_subnet,
delete_instance_template,
list_instance_templates,
)

from test_sample_start_stop import compute_instance
# Needed to make the flake8 happy.
assert compute_instance
m-strzelczyk marked this conversation as resolved.
Show resolved Hide resolved

PROJECT = google.auth.default()[1]

INSTANCE_ZONE = "europe-central2-b"


def test_create_instance():
template_name = "i" + uuid.uuid4().hex[:10]
template = create_template(PROJECT, template_name)

try:
assert template.name == template_name
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"
finally:
delete_instance_template(PROJECT, template_name)
m-strzelczyk marked this conversation as resolved.
Show resolved Hide resolved
assert all(
template.name != template_name for template in list_instance_templates(PROJECT)
)


def test_create_from_instance(compute_instance):
template_name = "i" + uuid.uuid4().hex[:10]
template = create_template_from_instance(
PROJECT, compute_instance.self_link, template_name
)

try:
assert template.name == 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"
)
finally:
delete_instance_template(PROJECT, template_name)


def test_create_template_with_subnet():
template_name = "i" + uuid.uuid4().hex[:10]
template = create_template_with_subnet(
PROJECT,
"global/networks/default",
"regions/asia-east1/subnetworks/default",
template_name,
)

try:
assert template.name == 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
)
finally:
delete_instance_template(PROJECT, template_name)