File: //snap/google-cloud-cli/current/lib/surface/privateca/templates/replicate.py
# -*- coding: utf-8 -*- #
# Copyright 2021 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.
"""Replicate a certificate template to multiple regions."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as api_exceptions
from googlecloudsdk.api_lib.privateca import base as privateca_base
from googlecloudsdk.api_lib.privateca import locations
from googlecloudsdk.api_lib.privateca import request_utils
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
import six
class ReplicationError(Exception):
"""Represents an error that occurred while replicating a resource to a given location."""
def __init__(self, location, message):
self._message = 'Failed to replicate to location [{}]: {}'.format(
location, message)
super(ReplicationError, self).__init__(self._message)
def __str__(self):
return self._message
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Replicate(base.SilentCommand):
"""Replicate a certificate template to multiple locations."""
detailed_help = {
'DESCRIPTION':
'Replicate a certificate template to multiple locations.',
'EXAMPLES':
"""\
To replicate a certificate templates to all supported locations, run:
$ {command} my-template --location=us-west1 --all-locations
To replicate a certificate template to 'us-west2' and 'us-east1', run:
$ {command} my-template --location=us-west1 --target-locations=us-west2,us-east1
To overwrite existing templates with the same resource ID in the target
locations, use the --overwrite flag:
$ {command} my-template --location=us-west1 --target-locations=us-west2,us-east1 --overwrite
To continue replicating templates in other locations in the event of a
failure in one or more locations, use the --continue-on-error flag:
$ {command} my-template --location=us-west1 --all-locations --continue-on-error""",
}
@staticmethod
def Args(parser):
resource_args.AddCertificateTemplatePositionalResourceArg(
parser, 'to replicate')
target_locations_group = base.ArgumentGroup(
mutex=True,
required=True,
help='Specify where the certificate template should be replicated.'
).AddToParser(parser)
base.Argument(
'--all-locations',
action='store_const',
const=True,
help='Replicate this template to all supported locations.').AddToParser(
target_locations_group)
base.Argument(
'--target-locations',
help='Replicate this template to the given locations.',
type=arg_parsers.ArgList(
element_type=lambda x: six.text_type(x).strip()),
metavar='LOCATION').AddToParser(target_locations_group)
base.Argument(
'--overwrite',
help=('Overwrite any existing templates with the same name, '
'if they exist.'),
action='store_const',
const=True,
default=False).AddToParser(parser)
base.Argument(
'--continue-on-error',
help=('Continue replicating the template to other locations '
'even if an error is encountered. If this is set, an '
'error in one location will be logged but will not '
'prevent replication to other locations.'),
action='store_const',
const=True,
default=False).AddToParser(parser)
def _CreateOrUpdateTemplate(self, project, location, template_id, template,
overwrite):
"""Returns an LRO for a Create or Update operation for the given template.
Args:
project: str, the project ID or number for the new template.
location: str, the location for the new template.
template_id: str, the resource ID for the new template.
template: object, the body of the new template.
overwrite: bool, whether to overwrite existing templates with the same ID.
Raises:
ReplicationError, if the template could not be replicated to this
location.
"""
parent = 'projects/{}/locations/{}'.format(project, location)
resource_name = '{}/certificateTemplates/{}'.format(parent, template_id)
try:
return self.client.projects_locations_certificateTemplates.Create(
self.messages
.PrivatecaProjectsLocationsCertificateTemplatesCreateRequest(
parent=parent,
certificateTemplateId=template_id,
certificateTemplate=template,
requestId=request_utils.GenerateRequestId()))
except api_exceptions.HttpConflictError as e:
if not overwrite:
raise ReplicationError(
location,
'Certificate template [{}] already exists and the --overwrite flag '
'was not set.'.format(resource_name))
return self.client.projects_locations_certificateTemplates.Patch(
self.messages
.PrivatecaProjectsLocationsCertificateTemplatesPatchRequest(
name=resource_name,
certificateTemplate=template,
# Always copy all fields. Mask value of '*' doesn't seem to be
# currently supported by CCFE.
updateMask='predefined_values,identity_constraints,passthrough_extensions,description,labels',
requestId=request_utils.GenerateRequestId()))
except api_exceptions.HttpError as e:
raise ReplicationError(location, six.text_type(e))
def Run(self, args):
"""Runs the command."""
self.client = privateca_base.GetClientInstance(api_version='v1')
self.messages = privateca_base.GetMessagesModule(api_version='v1')
template_ref = args.CONCEPTS.certificate_template.Parse()
template = self.client.projects_locations_certificateTemplates.Get(
self.messages.PrivatecaProjectsLocationsCertificateTemplatesGetRequest(
name=template_ref.RelativeName()))
# Name is output-only and will be different for each location.
template.name = ''
success_count = 0
target_locations = args.target_locations
if args.all_locations:
target_locations = [
location for location in locations.GetSupportedLocations('v1')
if location != template_ref.locationsId
]
for location in target_locations:
location = location.strip()
if location == template_ref.locationsId:
log.warning(
'Skipping location [{}] since it is the source location.'.format(
location))
continue
try:
operation = self._CreateOrUpdateTemplate(template_ref.projectsId,
location, template_ref.Name(),
template, args.overwrite)
operations.Await(
operation,
'Replicating template to [{}]'.format(location),
api_version='v1')
success_count += 1
except ReplicationError as e:
if args.continue_on_error:
log.warning(six.text_type(e))
continue
raise e
log.status.Print(
'Replicated template [{}] to {} out of {} locations.'.format(
template_ref.RelativeName(), success_count, len(target_locations)))