feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*- #
# Copyright 2016 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.
"""Wrapper for user-visible error exceptions to raise in the CLI."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.core import exceptions
class RuntimeConfigError(exceptions.Error):
"""Exceptions for RuntimeConfig errors."""
class WaitTimeoutError(RuntimeConfigError):
"""Raised when a wait operation times out."""

View File

@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*- #
# Copyright 2016 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.
"""Runtime-config resource transforms and symbols dict.
NOTICE: Each TransformFoo() method is the implementation of a foo() transform
function. Even though the implementation here is in Python the usage in resource
projection and filter expressions is language agnostic. This affects the
Pythonicness of the Transform*() methods:
(1) The docstrings are used to generate external user documentation.
(2) The method prototypes are included in the documentation. In particular the
prototype formal parameter names are stylized for the documentation.
(3) The types of some args, like r, are not fixed until runtime. Other args
may have either a base type value or string representation of that type.
It is up to the transform implementation to silently do the string=>type
conversions. That's why you may see e.g. int(arg) in some of the methods.
(4) Unless it is documented to do so, a transform function must not raise any
exceptions. The `undefined' arg is used to handle all unusual conditions,
including ones that would raise exceptions.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
# The DEADLINE_EXCEEDED error code.
DEADLINE_EXCEEDED = 4
def TransformWaiterStatus(r, undefined=''):
"""Returns a short description of the status of a waiter or waiter operation.
Status will be one of WAITING, SUCCESS, FAILURE, or TIMEOUT.
Args:
r: a JSON-serializable object
undefined: Returns this value if the resource status cannot be determined.
Returns:
One of WAITING, SUCCESS, FAILURE, or TIMEOUT
Example:
`--format="table(name, status())"`:::
Displays the status in table column two.
"""
if not isinstance(r, dict):
return undefined
if not r.get('done'):
return 'WAITING'
error = r.get('error')
if not error:
return 'SUCCESS'
if error.get('code') == DEADLINE_EXCEEDED:
return 'TIMEOUT'
else:
return 'FAILURE'
_TRANSFORMS = {
'waiter_status': TransformWaiterStatus,
}
def GetTransforms():
"""Returns the runtimeconfig-specific resource transform symbol table."""
return _TRANSFORMS

View File

@@ -0,0 +1,323 @@
# -*- coding: utf-8 -*- #
# Copyright 2016 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.
"""Common helper methods for Runtime Config commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import base64
import socket
from apitools.base.py import encoding
from googlecloudsdk.api_lib.runtime_config import exceptions as rtc_exceptions
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import exceptions as sdk_exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
from googlecloudsdk.core.console import progress_tracker
from googlecloudsdk.core.util import retry
import six
# The important substring from the error message "The read operation
# timed out".
TIMEOUT_ERR_TEXT = 'read operation timed out'
# The maximum number of seconds that a waiter timeout value can be set to.
# TODO(b/36050879): figure out proper maximum value
MAX_WAITER_TIMEOUT = 60 * 60 * 12 # 12 hours
# Default number of seconds to sleep between checking waiter status.
DEFAULT_WAITER_SLEEP = 5 # 5 seconds
# Length of the prefix before the short variable name.
VARIABLE_NAME_PREFIX_LENGTH = 5
def ProjectPath(project):
return '/'.join(['projects', project])
def ConfigPath(project, config):
return '/'.join([ProjectPath(project), 'configs', config])
def VariablePath(project, config, variable):
return '/'.join([ConfigPath(project, config), 'variables',
variable.lstrip('/')])
def WaiterPath(project, config, waiter):
return '/'.join([ConfigPath(project, config), 'waiters', waiter])
# TODO(b/36050485): these parse functions should live in command_lib.
def ParseConfigName(config_name):
"""Parse a config name or URL, and return a resource.
Args:
config_name: The config name.
Returns:
The parsed resource.
"""
params = {
'projectsId': Project
}
return resources.REGISTRY.Parse(config_name,
collection='runtimeconfig.projects.configs',
params=params)
def ParseVariableName(variable_name, args):
"""Parse a variable name or URL, and return a resource.
Args:
variable_name: The variable name.
args: CLI arguments, possibly containing a config name.
Returns:
The parsed resource.
"""
# Parameter values are lazily-evaluated only if they're actually necessary.
# If the user passes a full URL for the variable name, a separate
# --config-name parameter is not necessary. Without lazy evaluation,
# ConfigName function will raise an error if --config-name is unspecified,
# even if the variable name is a URL.
params = {
'projectsId': lambda: ParseConfigName(ConfigName(args)).projectsId,
'configsId': lambda: ParseConfigName(ConfigName(args)).configsId
}
return resources.REGISTRY.Parse(
variable_name,
collection='runtimeconfig.projects.configs.variables',
params=params)
def ParseWaiterName(waiter_name, args):
"""Parse a waiter name or URL, and return a resource.
Args:
waiter_name: The waiter name.
args: CLI arguments, possibly containing a config name.
Returns:
The parsed resource.
"""
params = {
'projectsId': lambda: ParseConfigName(ConfigName(args)).projectsId,
'configsId': lambda: ParseConfigName(ConfigName(args)).configsId
}
return resources.REGISTRY.Parse(
waiter_name,
collection='runtimeconfig.projects.configs.waiters',
params=params)
def ConfigName(args, required=True):
if required and not getattr(args, 'config_name', None):
raise sdk_exceptions.RequiredArgumentException(
'config', '--config-name parameter is required.')
return getattr(args, 'config_name', None)
def Client(timeout=None, num_retries=None):
client = apis.GetClientInstance('runtimeconfig', 'v1beta1')
if timeout is not None:
client.http.timeout = timeout
if num_retries is not None:
client.num_retries = num_retries
return client
def ConfigClient(**kwargs):
return Client(**kwargs).projects_configs
def VariableClient(**kwargs):
return Client(**kwargs).projects_configs_variables
def WaiterClient(**kwargs):
return Client(**kwargs).projects_configs_waiters
def Messages():
return apis.GetMessagesModule('runtimeconfig', 'v1beta1')
def Project(required=True):
return properties.VALUES.core.project.Get(required=required)
def IsBadGatewayError(error):
return getattr(error, 'status_code', None) == 502
def IsDeadlineExceededError(error):
return getattr(error, 'status_code', None) == 504
def IsSocketTimeout(error):
# For SSL timeouts, the error does not extend socket.timeout.
# There doesn't appear to be any way to differentiate an SSL
# timeout from any other SSL error other than checking the
# message. :(
return (isinstance(error, socket.timeout)
or TIMEOUT_ERR_TEXT in six.text_type(error))
def WaitForWaiter(waiter_resource, sleep=None, max_wait=None):
"""Wait for a waiter to finish.
Args:
waiter_resource: The waiter resource to wait for.
sleep: The number of seconds to sleep between status checks.
max_wait: The maximum number of seconds to wait before an error is raised.
Returns:
The last retrieved value of the Waiter.
Raises:
WaitTimeoutError: If the wait operation takes longer than the maximum wait
time.
"""
sleep = sleep if sleep is not None else DEFAULT_WAITER_SLEEP
max_wait = max_wait if max_wait is not None else MAX_WAITER_TIMEOUT
waiter_client = WaiterClient()
retryer = retry.Retryer(max_wait_ms=max_wait * 1000)
request = (waiter_client.client.MESSAGES_MODULE
.RuntimeconfigProjectsConfigsWaitersGetRequest(
name=waiter_resource.RelativeName()))
with progress_tracker.ProgressTracker(
'Waiting for waiter [{0}] to finish'.format(waiter_resource.Name())):
try:
result = retryer.RetryOnResult(waiter_client.Get,
args=[request],
sleep_ms=sleep * 1000,
should_retry_if=lambda w, s: not w.done)
except retry.WaitException:
raise rtc_exceptions.WaitTimeoutError(
'Waiter [{0}] did not finish within {1} seconds.'.format(
waiter_resource.Name(), max_wait))
if result.error is not None:
if result.error.message is not None:
message = 'Waiter [{0}] finished with an error: {1}'.format(
waiter_resource.Name(), result.error.message)
else:
message = 'Waiter [{0}] finished with an error.'.format(
waiter_resource.Name())
log.error(message)
return result
def IsFailedWaiter(waiter):
"""Returns True if the specified waiter has failed."""
return waiter.error is not None
def _DictWithShortName(message, name_converter):
"""Returns a dict representation of the message with a shortened name value.
This method does three things:
1. converts message to a dict.
2. shortens the value of the name field using name_converter
3. sets atomicName to the original value of name.
Args:
message: A protorpclite message.
name_converter: A function that takes an atomic name as a parameter and
returns a shortened name.
Returns:
A dict representation of the message with a shortened name field.
Raises:
ValueError: If the original message already contains an atomicName field.
"""
message_dict = encoding.MessageToDict(message)
# Defend against the unlikely scenario where the original message
# already has an 'atomicName' field.
if 'name' in message_dict:
if 'atomicName' in message_dict:
raise ValueError('Original message cannot contain an atomicName field.')
message_dict['atomicName'] = message_dict['name']
message_dict['name'] = name_converter(message_dict['name'])
return message_dict
def FormatConfig(message):
"""Returns the config message as a dict with a shortened name."""
# Example name:
# "projects/my-project/configs/my-config"
# name.split('/')[-1] returns 'my-config'.
return _DictWithShortName(message, lambda name: name.split('/')[-1])
def FormatVariable(message, output_value=False):
"""Returns the variable message as a dict with a shortened name.
This method first converts the variable message to a dict with a shortened
name and an atomicName. Then, decodes the variable value in the dict if the
output_value flag is True.
Args:
message: A protorpclite message.
output_value: A bool flag indicates whether we want to decode and output the
values of the variables. The default value of this flag is False.
Returns:
A dict representation of the message with a shortened name field.
"""
# Example name:
# "projects/my-project/configs/my-config/variables/my/var"
# '/'.join(name.split('/')[5:]) returns 'my/var'
message_dict = _DictWithShortName(
message,
lambda name: '/'.join(name.split('/')[VARIABLE_NAME_PREFIX_LENGTH:]))
if output_value:
# A variable always has either a "text" field or a base64-encoded "value"
# field but not both.
if 'text' in message_dict:
message_dict['value'] = message_dict['text']
else:
message_dict['value'] = base64.b64decode(message_dict['value'])
return message_dict
def FormatWaiter(message):
"""Returns the waiter message as a dict with a shortened name."""
# Example name:
# "projects/my-project/configs/my-config/waiters/my-waiter"
# name.split('/')[-1] returns 'my-waiter'
return _DictWithShortName(message, lambda name: name.split('/')[-1])