File: //snap/google-cloud-cli/current/lib/googlecloudsdk/command_lib/compute/disks/create.py
# -*- coding: utf-8 -*- #
# Copyright 2017 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utility functions for create command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.compute import utils
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.core import properties
import six
from six.moves import range # pylint: disable=redefined-builtin
def ParseRegionDisksResources(resources, disks, replica_zones, project,
region):
"""Parse disks arguments taking into account project, region and zones.
Try to deduce --region from --replica-zones and parse disk references:
0. parse --project
1. parse --region falling back to 0 for project
2. for each disk:
2.1. parse disk falling back to 0 and 1 falling back to property if necessary
2.2. extract disk project from 2.1
2.3. parse --replica-zones falling back to 2.2
2.4. check zones are in disk project
2.5. check zones are from the same region
2.6. if --region is present, check if equal to 2.5
2.7. parse disk falling back to 2.2 and 2.5
2.8. check if disk is in 2.5 region
2.9. yield 2.7
Function is greedy - checks/deduces/parses all data before returning. If any
error occurs, exception is raised.
Args:
resources: resources.Registry: resource parser
disks: str, parsed disks argument (args.DISK_NAME)
replica_zones: str, parsed --replica-zones flag (args.replica_zones)
project: str, parsed --project flag or None (args.project)
region: str, parsed --region flag or None (args.region)
Returns:
List disk resources [compute.regionDisks]
"""
result_disks = []
project_to_region = {} # cache
sample = '$SAMPLE$' # shouldn't escape from this function
# --project may be provided or not, URI should be accepted
project_res = resources.Parse(
project, collection='compute.projects', params={'project': sample})
project_name = project_res.project
# --region may be provided or not, URI should be accepted
# project embedded in URI takes precedence over project_res
region_res = resources.Parse(
region,
collection='compute.regions',
params={'project': project_name,
'region': sample})
if region_res.project != project_name:
project_name = region_res.project
region_name = region_res.region
if project_name == sample:
# no project in --project nor --region - fallback to property
project_name = properties.VALUES.core.project.GetOrFail
# parse each disk separately as meaning of other flags may depend on disk URI
for disk in disks:
result_disk = _ParseDisk(resources, disk, sample, project_name,
project_to_region, region, region_name,
replica_zones)
result_disks.append(result_disk)
return result_disks
def _ParseDisk(resources, disk, sample, project_name, project_to_region,
region, region_name, replica_zones):
"""Parse single disk reference."""
# I need project to parse zone URI - parse disk argument, stage 1
disk_resource = resources.Parse(
disk,
params={
'region': region_name,
'project': project_name
},
collection='compute.regionDisks')
current_project = disk_resource.project
# maintain cache
if current_project not in project_to_region:
project_to_region[current_project] = _DeduceRegionInProject(
resources, current_project, disk_resource, sample, region,
region_name, replica_zones)
# parse disk argument using real region, stage 2
# doesn't support scope listing/prompting, because scope is already chosen.
result_disk = resources.Parse(
disk,
collection='compute.regionDisks',
params={
'region': project_to_region[current_project],
'project': current_project
})
if result_disk.region != project_to_region[current_project]:
raise exceptions.InvalidArgumentException('--replica-zones', (
'Region from [DISK_NAME] ({}) is different from [--replica-zones] '
'({}).').format(result_disk.SelfLink(),
project_to_region[current_project]))
return result_disk
def _DeduceRegionInProject(resources, current_project, disk_resource,
sample, region, region_name, replica_zones):
"""Deduce region from zones in given project."""
# parse all --replica-zones, consuming project from above
current_zones = [
resources.Parse(
zone, collection='compute.zones', params={'project': current_project})
for zone in replica_zones
]
# check if all zones live in disks' project
for zone in current_zones:
if zone.project != current_project:
raise exceptions.InvalidArgumentException(
'--zone',
'Zone [{}] lives in different project than disk [{}].'.format(
six.text_type(zone.SelfLink()),
six.text_type(disk_resource.SelfLink())))
# check if all zones live in the same region
for i in range(len(current_zones) - 1):
if (utils.ZoneNameToRegionName(current_zones[i].zone) !=
utils.ZoneNameToRegionName(current_zones[i + 1].zone)):
raise exceptions.InvalidArgumentException('--replica-zones', (
'Zones [{}, {}] live in different regions [{}, {}], but should '
'live in the same.').format(
current_zones[i].zone, current_zones[i + 1].zone,
utils.ZoneNameToRegionName(current_zones[i].zone),
utils.ZoneNameToRegionName(current_zones[i + 1].zone)))
# check if --replica-zones is consistent with --region
result = utils.ZoneNameToRegionName(current_zones[0].zone)
if region is not None and region_name != sample and region_name != result:
raise exceptions.InvalidArgumentException('--replica-zones', (
'Region from [--replica-zones] ({}) is different from [--region] '
'({}).').format(result, region_name))
return result