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,35 @@
# -*- 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.
"""Common utility functions for getting the SQL API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.core import resources
# API version constants
API_VERSION_DEFAULT = 'v1beta4'
class SqlClient(object):
"""Wrapper for SQL API client and associated resources."""
def __init__(self, api_version):
self.sql_client = apis.GetClientInstance('sql', api_version)
self.sql_messages = self.sql_client.MESSAGES_MODULE
self.resource_parser = resources.Registry()
self.resource_parser.RegisterApiByName('sql', api_version)

View File

@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*- #
# Copyright 2015 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 cert utility functions for sql instances."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
def GetCertRefFromName(sql_client, sql_messages, resources, instance_ref,
common_name):
"""Get a cert reference for a particular instance, given its common name.
Args:
sql_client: apitools.BaseApiClient, A working client for the sql version to
be used.
sql_messages: module, The module that defines the messages for the sql
version to be used.
resources: resources.Registry, The registry that can create resource refs
for the sql version to be used.
instance_ref: resources.Resource, The instance whos ssl cert is being
fetched.
common_name: str, The common name of the ssl cert to be fetched.
Returns:
resources.Resource, A ref for the ssl cert being fetched. Or None if it
could not be found.
"""
cert = GetCertFromName(sql_client, sql_messages, instance_ref, common_name)
if not cert:
return None
return resources.Create(
collection='sql.sslCerts',
project=instance_ref.project,
instance=instance_ref.instance,
sha1Fingerprint=cert.sha1Fingerprint)
def GetCertFromName(sql_client, sql_messages, instance_ref, common_name):
"""Get a cert for a particular instance, given its common name.
In the SQL API, the last parameter of the URL is the sha1fingerprint, which is
not something writeable or readable by humans. Instead, the CLI will ask for
the common name. To allow this, we first query all the ssl certs for the
instance, and iterate through them to find the one with the correct common
name.
Args:
sql_client: apitools.BaseApiClient, A working client for the sql version to
be used.
sql_messages: module, The module that defines the messages for the sql
version to be used.
instance_ref: resources.Resource, The instance whos ssl cert is being
fetched.
common_name: str, The common name of the ssl cert to be fetched.
Returns:
resources.Resource, A ref for the ssl cert being fetched. Or None if it
could not be found.
"""
certs = sql_client.sslCerts.List(
sql_messages.SqlSslCertsListRequest(
project=instance_ref.project, instance=instance_ref.instance))
for cert in certs.items:
if cert.commonName == common_name:
return cert
return None

View File

@@ -0,0 +1,68 @@
# -*- 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.
"""Defines tool-wide constants."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
# Defaults for instance creation.
DEFAULT_MACHINE_TYPE = 'db-n1-standard-1'
# Determining what executables, flags, and defaults to use for sql connect.
DB_EXE = {'MYSQL': 'mysql', 'POSTGRES': 'psql', 'SQLSERVER': 'sqlcmd'}
EXE_FLAGS = {
'mysql': {
'user': '-u',
'password': '-p',
'hostname': '-h',
'port': '-P'
},
'psql': {
'user': '-U',
'password': '-W',
'hostname': '-h',
'port': '-p',
'database': '-d'
},
'sqlcmd': {
'user': '-U',
'hostname': '-S',
'database': '-d'
}
}
DEFAULT_SQL_USER = {
'mysql': 'root',
'psql': 'postgres',
'sqlcmd': 'sqlserver'
}
# Size conversions.
BYTES_TO_GB = 1 << 30
# Cloud SQL Proxy constants.
# Generally unassigned port number for the proxy to bind to.
DEFAULT_PROXY_PORT_NUMBER = 9470
PROXY_ADDRESS_IN_USE_ERROR = 'bind: address already in use'
PROXY_READY_FOR_CONNECTIONS_MSG = 'Ready for new connections'
PROXY_V2_READY_FOR_CONNECTIONS_MSG = (
'The proxy has started successfully and is ready for new connections!'
)

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*- #
# Copyright 2015 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 utility functions for sql errors and exceptions."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.core import exceptions
class Error(exceptions.Error):
"""Base exception for all sql errors."""
class ArgumentError(Error):
"""Command argument error."""
class CancelledError(Error):
"""An exception raised if the user chooses not to continue."""
class OperationError(Error):
"""An exception raised if an operation encounters an error."""
class ResourceNotFoundError(Error):
"""An exception raised when a resource could not be found by the server."""
class SqlClientNotFoundError(Error):
"""An exception raised when a locally installed sql client cannot be found."""
class ConnectionError(Error):
"""An exception raised when a connection could not be made to a sql instance.
"""
class UpdateError(Error):
"""An error raised when a connection could not be made to a sql instance."""
class CloudSqlProxyError(Error):
"""An error raised when the Cloud SQL Proxy fails to start."""
class InvalidStateError(Error):
"""An error raised when a Cloud SQL resource is in an invalid state."""
class SqlProxyNotFound(Error):
"""An error raised when no sql proxy found."""

View File

@@ -0,0 +1,226 @@
# -*- 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.
"""Common command-agnostic utility functions for sql export commands."""
def ParseBakType(sql_messages, bak_type):
if bak_type is None:
return (
sql_messages.ExportContext.BakExportOptionsValue.BakTypeValueValuesEnum.FULL
)
return sql_messages.ExportContext.BakExportOptionsValue.BakTypeValueValuesEnum.lookup_by_name(
bak_type.upper()
)
def SqlExportContext(
sql_messages,
uri,
database=None,
table=None,
offload=False,
parallel=False,
threads=None,
clean=False,
if_exists=False,
):
"""Generates the ExportContext for the given args, for exporting to SQL.
Args:
sql_messages: module, The messages module that should be used.
uri: The URI of the bucket to export to; the output of the 'uri' arg.
database: The list of databases to export from; the output of the
'--database' flag.
table: The list of tables to export from; the output of the '--table' flag.
offload: bool, The export offload flag.
parallel: Whether to use parallel export or not.
threads: The number of threads to use. Only applicable for parallel export.
clean: Whether to include SQL statement (DROP <object>) required to drop
database objects prior to import; corresponds to the clean flag on pg_dump.
Only applies to PostgreSQL non-parallel exports.
if_exists: Whether to include SQL statement (IF EXISTS) to each drop
statement produced by the clean flag; corresponds to the if-exists flag on
pg_dump. Only applies to PostgreSQL non-parallel exports.
Returns:
ExportContext, for use in InstancesExportRequest.exportContext.
"""
if parallel:
return sql_messages.ExportContext(
kind='sql#exportContext',
uri=uri,
databases=database or [],
offload=offload,
fileType=sql_messages.ExportContext.FileTypeValueValuesEnum.SQL,
sqlExportOptions=sql_messages.ExportContext.SqlExportOptionsValue(
tables=table or [], parallel=parallel, threads=threads
),
)
else:
postgres_export_options = (
sql_messages.
ExportContext.SqlExportOptionsValue.PostgresExportOptionsValue(
clean=clean, ifExists=if_exists
)
)
return sql_messages.ExportContext(
kind='sql#exportContext',
uri=uri,
databases=database or [],
offload=offload,
fileType=sql_messages.ExportContext.FileTypeValueValuesEnum.SQL,
sqlExportOptions=sql_messages.ExportContext.SqlExportOptionsValue(
tables=table or [],
threads=threads,
postgresExportOptions=postgres_export_options,
),
)
def CsvExportContext(sql_messages,
uri,
database=None,
query=None,
offload=False,
quote=None,
escape=None,
fields_terminated_by=None,
lines_terminated_by=None):
"""Generates the ExportContext for the given args, for exporting to CSV.
Args:
sql_messages: module, The messages module that should be used.
uri: The URI of the bucket to export to; the output of the 'uri' arg.
database: The list of databases to export from; the output of the
'--database' flag.
query: The query string to use to generate the table; the output of the
'--query' flag.
offload: bool, The export offload flag.
quote: character in Hex. The quote character for CSV format; the output of
the '--quote' flag.
escape: character in Hex. The escape character for CSV format; the output of
the '--escape' flag.
fields_terminated_by: character in Hex. The fields delimiter character for
CSV format; the output of the '--fields-terminated-by' flag.
lines_terminated_by: character in Hex. The lines delimiter character for CSV
format; the output of the '--lines-terminated-by' flag.
Returns:
ExportContext, for use in InstancesExportRequest.exportContext.
"""
return sql_messages.ExportContext(
kind='sql#exportContext',
uri=uri,
databases=database or [],
offload=offload,
fileType=sql_messages.ExportContext.FileTypeValueValuesEnum.CSV,
csvExportOptions=sql_messages.ExportContext.CsvExportOptionsValue(
selectQuery=query,
quoteCharacter=quote,
escapeCharacter=escape,
fieldsTerminatedBy=fields_terminated_by,
linesTerminatedBy=lines_terminated_by))
def BakExportContext(
sql_messages,
uri,
database,
stripe_count,
striped,
bak_type,
differential_base,
export_log_start_time,
export_log_end_time,
):
"""Generates the ExportContext for the given args, for exporting to BAK.
Args:
sql_messages: module, The messages module that should be used.
uri: The URI of the bucket to export to; the output of the 'uri' arg.
database: The list of databases to export from; the output of the
'--database' flag.
stripe_count: How many stripes to perform the export with.
striped: Whether the export should be striped.
bak_type: Type of bak file that will be exported. SQL Server only.
differential_base: Whether the bak file export can be used as differential
base for future differential backup. SQL Server only.
export_log_start_time: start time of the log export. SQL Server only.
export_log_end_time: end time of the log export. SQL Server only.
Returns:
ExportContext, for use in InstancesExportRequest.exportContext.
"""
bak_export_options = sql_messages.ExportContext.BakExportOptionsValue()
if striped is not None:
bak_export_options.striped = striped
if stripe_count is not None:
bak_export_options.stripeCount = stripe_count
bak_export_options.differentialBase = differential_base
bak_export_options.bakType = ParseBakType(sql_messages, bak_type)
if export_log_start_time is not None:
bak_export_options.exportLogStartTime = export_log_start_time.strftime(
'%Y-%m-%dT%H:%M:%S.%fZ'
)
if export_log_end_time is not None:
bak_export_options.exportLogEndTime = export_log_end_time.strftime(
'%Y-%m-%dT%H:%M:%S.%fZ'
)
return sql_messages.ExportContext(
kind='sql#exportContext',
uri=uri,
databases=database,
fileType=sql_messages.ExportContext.FileTypeValueValuesEnum.BAK,
bakExportOptions=bak_export_options)
def TdeExportContext(
sql_messages,
certificate,
cert_path,
pvk_path,
pvk_password,
):
"""Generates the ExportContext for the given args, for exporting to TDE.
Args:
sql_messages: module, The messages module that should be used.
certificate: The certificate name; the output of the
`--certificate` flag.
cert_path: The certificate path in Google Cloud Storage; the output of the
`--cert-path` flag.
pvk_path: The private key path in Google Cloud Storage; the output of the
`--pvk-path` flag.
pvk_password: The password that encrypts the private key; the output
of the `--pvk-password` or `--prompt-for-pvk-password` flag.
Returns:
ExportContext, for use in InstancesImportRequest.exportContext.
"""
tde_export_options = sql_messages.ExportContext.TdeExportOptionsValue(
name=certificate,
certificatePath=cert_path,
privateKeyPath=pvk_path,
privateKeyPassword=pvk_password)
return sql_messages.ExportContext(
kind='sql#exportContext',
fileType=sql_messages.ExportContext.FileTypeValueValuesEnum.TDE,
tdeExportOptions=tde_export_options)

View File

@@ -0,0 +1,242 @@
# -*- 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.
"""Common command-agnostic utility functions for sql import commands."""
def ParseBakType(sql_messages, bak_type):
if bak_type is None:
return (
sql_messages.ImportContext.BakImportOptionsValue.BakTypeValueValuesEnum.FULL
)
return sql_messages.ImportContext.BakImportOptionsValue.BakTypeValueValuesEnum.lookup_by_name(
bak_type.upper()
)
def SqlImportContext(
sql_messages,
uri,
database=None,
user=None,
parallel=False,
threads=None,
clean=False,
if_exists=False,
):
"""Generates the ImportContext for the given args, for importing from SQL.
Args:
sql_messages: module, The messages module that should be used.
uri: The URI of the bucket to import from; the output of the 'uri' arg.
database: The database to import to; the output of the '--database' flag.
user: The Postgres user to import as; the output of the '--user' flag.
parallel: Whether to use parallel import or not; the output of the
'--parallel' flag.
threads: The number of threads to use; the output of the '--threads' flag.
Only applicable for parallel import.
clean: Clean (DROP) database objects before recreating them. Corresponds to
the --clean flag on pg_restore. Only applies if --parallel is set.
PostgreSQL only.
if_exists: Include SQL statement (IF EXISTS) with each
DROP statement produced by --clean; Corresponds to the --if-exists flag
on pg_restore. Only applies if --parallel is set. PostgreSQL only.
Returns:
ImportContext, for use in InstancesImportRequest.importContext.
"""
if parallel:
postgres_import_options = None
if clean or if_exists:
postgres_import_options = (
sql_messages.ImportContext.SqlImportOptionsValue
.PostgresImportOptionsValue(
clean=clean,
ifExists=if_exists,
)
)
return sql_messages.ImportContext(
kind='sql#importContext',
uri=uri,
database=database,
fileType=sql_messages.ImportContext.FileTypeValueValuesEnum.SQL,
importUser=user,
sqlImportOptions=sql_messages.ImportContext.SqlImportOptionsValue(
parallel=parallel,
threads=threads,
postgresImportOptions=postgres_import_options,
),
)
else:
return sql_messages.ImportContext(
kind='sql#importContext',
uri=uri,
database=database,
fileType=sql_messages.ImportContext.FileTypeValueValuesEnum.SQL,
importUser=user,
sqlImportOptions=sql_messages.ImportContext.SqlImportOptionsValue(
threads=threads
),
)
def CsvImportContext(sql_messages,
uri,
database,
table,
columns=None,
user=None,
quote=None,
escape=None,
fields_terminated_by=None,
lines_terminated_by=None):
"""Generates the ImportContext for the given args, for importing from CSV.
Args:
sql_messages: module, The messages module that should be used.
uri: The URI of the bucket to import from; the output of the 'uri' arg.
database: The database to import into; the output of the '--database' flag.
table: The table to import into; the output of the '--table' flag.
columns: The CSV columns to import form; the output of the '--columns' flag.
user: The Postgres user to import as; the output of the '--user' flag.
quote: character in Hex. The quote character for CSV format; the output of
the '--quote' flag.
escape: character in Hex. The escape character for CSV format; the output of
the '--escape' flag.
fields_terminated_by: character in Hex. The fields delimiter character for
CSV format; the output of the '--fields-terminated-by' flag.
lines_terminated_by: character in Hex. The lines delimiter character for CSV
format; the output of the '--lines-terminated-by' flag.
Returns:
ImportContext, for use in InstancesImportRequest.importContext.
"""
return sql_messages.ImportContext(
kind='sql#importContext',
csvImportOptions=sql_messages.ImportContext.CsvImportOptionsValue(
columns=columns or [], table=table,
quoteCharacter=quote,
escapeCharacter=escape,
fieldsTerminatedBy=fields_terminated_by,
linesTerminatedBy=lines_terminated_by),
uri=uri,
database=database,
fileType=sql_messages.ImportContext.FileTypeValueValuesEnum.CSV,
importUser=user)
def BakImportContext(
sql_messages,
uri,
database,
cert_path,
pvk_path,
pvk_password,
keep_encrypted,
striped,
no_recovery,
recovery_only,
bak_type,
stop_at,
stop_at_mark,
):
"""Generates the ImportContext for the given args, for importing from BAK.
Args:
sql_messages: module, The messages module that should be used.
uri: The URI of the bucket to import from; the output of the `uri` arg.
database: The database to import to; the output of the `--database` flag.
cert_path: The certificate used for encrypted .bak; the output of the
`--cert-path` flag.
pvk_path: The private key used for encrypted .bak; the output of the
`--pvk-path` flag.
pvk_password: The private key password used for encrypted .bak; the output
of the `--pvk-password` or `--prompt-for-pvk-password` flag.
keep_encrypted: Whether or not to decrypt the imported encrypted BAK file.
striped: Whether or not the import is striped.
no_recovery: Whether the import executes with NORECOVERY keyword.
recovery_only: Whether the import skip download and bring database online.
bak_type: Type of the bak file.
stop_at: Equivalent to SQL Server STOPAT keyword.
stop_at_mark: Equivalent to SQL Server STOPATMARK keyword.
Returns:
ImportContext, for use in InstancesImportRequest.importContext.
"""
bak_import_options = None
if cert_path and pvk_path and pvk_password:
bak_import_options = sql_messages.ImportContext.BakImportOptionsValue(
encryptionOptions=sql_messages.ImportContext.BakImportOptionsValue
.EncryptionOptionsValue(
certPath=cert_path,
pvkPath=pvk_path,
pvkPassword=pvk_password,
))
if keep_encrypted:
bak_import_options.encryptionOptions.keepEncrypted = keep_encrypted
else:
bak_import_options = sql_messages.ImportContext.BakImportOptionsValue()
if striped:
bak_import_options.striped = striped
bak_import_options.noRecovery = no_recovery
bak_import_options.recoveryOnly = recovery_only
bak_import_options.bakType = ParseBakType(sql_messages, bak_type)
if stop_at is not None:
bak_import_options.stopAt = stop_at.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
bak_import_options.stopAtMark = stop_at_mark
return sql_messages.ImportContext(
kind='sql#importContext',
uri=uri,
database=database,
fileType=sql_messages.ImportContext.FileTypeValueValuesEnum.BAK,
bakImportOptions=bak_import_options)
def TdeImportContext(
sql_messages,
certificate,
cert_path,
pvk_path,
pvk_password,
):
"""Generates the ImportContext for the given args, for importing from TDE.
Args:
sql_messages: module, The messages module that should be used.
certificate: The certificate name; the output of the
`--certificate` flag.
cert_path: The certificate path in Google Cloud Storage; the output of the
`--cert-path` flag.
pvk_path: The private key path in Google Cloud Storage; the output of the
`--pvk-path` flag.
pvk_password: The password that encrypts the private key; the output
of the `--pvk-password` or `--prompt-for-pvk-password` flag.
Returns:
ImportContext, for use in InstancesImportRequest.importContext.
"""
tde_import_options = sql_messages.ImportContext.TdeImportOptionsValue(
name=certificate,
certificatePath=cert_path,
privateKeyPath=pvk_path,
privateKeyPassword=pvk_password)
return sql_messages.ImportContext(
kind='sql#importContext',
fileType=sql_messages.ImportContext.FileTypeValueValuesEnum.TDE,
tdeImportOptions=tde_import_options)

View File

@@ -0,0 +1,482 @@
# -*- 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 utility functions for sql instances."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import errno
import os
import subprocess
import time
from apitools.base.py import list_pager
from googlecloudsdk.api_lib.sql import api_util
from googlecloudsdk.api_lib.sql import constants
from googlecloudsdk.api_lib.sql import exceptions as sql_exceptions
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.core import config
from googlecloudsdk.core import execution_utils
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import encoding
from googlecloudsdk.core.util import files as file_utils
import six
messages = apis.GetMessagesModule('sql', 'v1beta4')
_BASE_CLOUD_SQL_PROXY_ERROR = 'Failed to start the Cloud SQL Proxy'
_MYSQL_DATABASE_VERSION_PREFIX = 'MYSQL'
_POSTGRES_DATABASE_VERSION_PREFIX = 'POSTGRES'
_SQLSERVER_DATABASE_VERSION_PREFIX = 'SQLSERVER'
class DatabaseInstancePresentation(object):
"""Represents a DatabaseInstance message that is modified for user visibility."""
def __init__(self, orig):
for field in orig.all_fields():
if field.name == 'state':
if orig.settings and orig.settings.activationPolicy == messages.Settings.ActivationPolicyValueValuesEnum.NEVER:
self.state = 'STOPPED'
else:
self.state = orig.state
else:
value = getattr(orig, field.name)
if value is not None and not (isinstance(value, list) and not value):
if field.name in ['currentDiskSize', 'maxDiskSize']:
setattr(self, field.name, six.text_type(value))
else:
setattr(self, field.name, value)
def __eq__(self, other):
"""Overrides the default implementation by checking attribute dicts."""
if isinstance(other, DatabaseInstancePresentation):
return self.__dict__ == other.__dict__
return False
def __ne__(self, other):
"""Overrides the default implementation (only needed for Python 2)."""
return not self.__eq__(other)
def GetRegionFromZone(gce_zone):
"""Parses and returns the region string from the gce_zone string."""
zone_components = gce_zone.split('-')
# The region is all but the last component of the zone.
return '-'.join(zone_components[:-1])
def _IsCloudSqlProxyPresentInSdkBin(cloud_sql_proxy_path):
"""Checks if cloud_sql_proxy_path binary is present in cloud sdk bin."""
return (os.path.exists(cloud_sql_proxy_path) and
os.access(cloud_sql_proxy_path, os.X_OK))
def _GetCloudSqlProxyPath(binary_name='cloud_sql_proxy'):
"""Determines the path to the Cloud SQL Proxy binary.
Args:
binary_name: Name of Cloud SQL Proxy binary to look for in path. (v1:
'cloud_sql_proxy', v2: 'cloud-sql-proxy')
Returns:
str: The path to the Cloud SQL Proxy binary.
"""
sdk_bin_path = config.Paths().sdk_bin_path
if sdk_bin_path:
# Validate if the Cloud SQL Proxy binary is present in Cloud SDK bin
# repository and it has access for invoking user,
# otherwise look for it in PATH
cloud_sql_proxy_path = os.path.join(sdk_bin_path, binary_name)
if _IsCloudSqlProxyPresentInSdkBin(cloud_sql_proxy_path):
return cloud_sql_proxy_path
# Check if Cloud SQL Proxy is located on the PATH.
proxy_path = file_utils.FindExecutableOnPath(binary_name)
if proxy_path:
log.debug(f'Using {binary_name} found at [{proxy_path}]')
return proxy_path
else:
raise sql_exceptions.SqlProxyNotFound(
'A Cloud SQL Proxy SDK root could not be found, or access is denied.'
'Please check your installation.')
def _RaiseProxyError(error_msg=None):
message = '{}.'.format(_BASE_CLOUD_SQL_PROXY_ERROR)
if error_msg:
message = '{}: {}'.format(_BASE_CLOUD_SQL_PROXY_ERROR, error_msg)
raise sql_exceptions.CloudSqlProxyError(message)
def _ReadLineFromStderr(proxy_process):
"""Reads and returns the next line from the proxy stderr stream."""
return encoding.Decode(proxy_process.stderr.readline())
def _ReadLineFromStdout(proxy_process):
"""Reads and returns the next line from the proxy stdout stream."""
return encoding.Decode(proxy_process.stdout.readline())
def _WaitForProxyV2ToStart(
proxy_process, port, seconds_to_timeout, run_connection_test=False
):
"""Wait for the proxy to be ready for connections, then return proxy_process.
Args:
proxy_process: The Process corresponding to the Cloud SQL Proxy (v2).
port: int, the port that the proxy was started on.
seconds_to_timeout: Seconds to wait before timing out.
run_connection_test: if true, waits for connection test success message.
Returns:
The Process object corresponding to the Cloud SQL Proxy (v2).
"""
total_wait_seconds = 0
seconds_to_sleep = 0.2
while proxy_process.poll() is None:
line = _ReadLineFromStdout(proxy_process)
while line:
log.status.write(line)
if constants.PROXY_ADDRESS_IN_USE_ERROR in line:
_RaiseProxyError(
'Port already in use. Exit the process running on port {} or try '
'connecting again on a different port.'.format(port)
)
elif run_connection_test and 'Connection test passed' in line:
return proxy_process
elif (
not run_connection_test
and constants.PROXY_V2_READY_FOR_CONNECTIONS_MSG in line
):
# The proxy is ready to go, so stop polling!
return proxy_process
line = _ReadLineFromStdout(proxy_process)
# If we've been waiting past the timeout, throw an error.
if total_wait_seconds >= seconds_to_timeout:
_RaiseProxyError('Timed out.')
# Keep polling on the proxy output until relevant lines are found.
total_wait_seconds += seconds_to_sleep
time.sleep(seconds_to_sleep)
# If we've reached this point, the proxy process exited.
# If running connection test, we check stdout for success message.
if run_connection_test:
while True:
line = _ReadLineFromStdout(proxy_process)
if not line:
break
log.status.write(line)
if 'Connection test passed' in line:
return proxy_process
err = proxy_process.stderr.read()
if err:
decoded_err = encoding.Decode(err)
err_lines = decoded_err.splitlines()
error_only_lines = []
for line in err_lines:
if line.startswith('Usage:'):
break
error_only_lines.append(line)
error_msg = '\n'.join(error_only_lines).strip()
if error_msg:
_RaiseProxyError(error_msg)
else:
_RaiseProxyError(decoded_err)
else:
_RaiseProxyError()
def _WaitForProxyToStart(proxy_process, port, seconds_to_timeout):
"""Wait for the proxy to be ready for connections, then return proxy_process.
Args:
proxy_process: Process, the process corresponding to the Cloud SQL Proxy.
port: int, the port that the proxy was started on.
seconds_to_timeout: Seconds to wait before timing out.
Returns:
The Process object corresponding to the Cloud SQL Proxy.
"""
total_wait_seconds = 0
seconds_to_sleep = 0.2
while proxy_process.poll() is None:
line = _ReadLineFromStderr(proxy_process)
while line:
log.status.write(line)
if constants.PROXY_ADDRESS_IN_USE_ERROR in line:
_RaiseProxyError(
'Port already in use. Exit the process running on port {} or try '
'connecting again on a different port.'.format(port))
elif constants.PROXY_READY_FOR_CONNECTIONS_MSG in line:
# The proxy is ready to go, so stop polling!
return proxy_process
line = _ReadLineFromStderr(proxy_process)
# If we've been waiting past the timeout, throw an error.
if total_wait_seconds >= seconds_to_timeout:
_RaiseProxyError('Timed out.')
# Keep polling on the proxy output until relevant lines are found.
total_wait_seconds += seconds_to_sleep
time.sleep(seconds_to_sleep)
# If we've reached this point, the proxy process exited unexpectedly.
_RaiseProxyError()
def StartCloudSqlProxyV2(instance,
port,
seconds_to_timeout=10,
impersonate_service_account=None,
auto_iam_authn=False,
private_ip=False,
psc=False,
auto_ip=False,
debug_logs=False,
run_connection_test=False):
"""Starts the Cloud SQL Proxy (v2) for instance on the given port.
Args:
instance: The instance to start the proxy for.
port: The port to bind the proxy to.
seconds_to_timeout: Seconds to wait before timing out.
impersonate_service_account: Service account to impersonate.
auto_iam_authn: Whether to use IAM DB Authentication.
private_ip: Whether to use private IP.
psc: Whether to use PSC.
auto_ip: Whether to use auto IP detection.
debug_logs: Whether to enable verbose logs in proxy.
run_connection_test: Whether to run connection test.
Returns:
The Process object corresponding to the Cloud SQL Proxy.
Raises:
CloudSqlProxyError: An error starting the Cloud SQL Proxy.
SqlProxyNotFound: An error finding a Cloud SQL Proxy installation.
"""
command_path = _GetCloudSqlProxyPath(binary_name='cloud-sql-proxy')
# Specify the instance and port to connect with.
args = [instance.connectionName, '--port', str(port)]
if private_ip:
args.append('--private-ip')
if psc:
args.append('--psc')
if auto_ip:
args.append('--auto-ip')
if debug_logs:
args.append('--debug-logs')
if auto_iam_authn:
args.append('--auto-iam-authn')
if properties.VALUES.api_endpoint_overrides.sql.Get():
args += [
'--sqladmin-api-endpoint',
properties.VALUES.api_endpoint_overrides.sql.Get(),
]
if run_connection_test:
args.append('--run-connection-test')
if impersonate_service_account:
args += ['--impersonate-service-account', impersonate_service_account]
proxy_args = execution_utils.ArgsForExecutableTool(command_path, *args)
log.status.write(
'Starting Cloud SQL Proxy: [{args}]\n'.format(args=' '.join(proxy_args))
)
try:
proxy_process = subprocess.Popen(
proxy_args,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except EnvironmentError as e:
if e.errno == errno.ENOENT:
# We are trying to catch here the 'file not found error', so that a proper
# message can be sent to the user with installation help.
raise sql_exceptions.CloudSqlProxyError(
'Failed to start Cloud SQL Proxy. Please make sure it is available in'
' the PATH [{}]. Learn more about installing the Cloud SQL Proxy here'
': https://cloud.google.com/sql/docs/mysql/connect-auth-proxy#install'
'. If you would like to report this issue, please run the following '
'command: gcloud feedback'.format(command_path)
)
# Else raise the EnvironmentError.
raise
return _WaitForProxyV2ToStart(
proxy_process, port, seconds_to_timeout, run_connection_test
)
def StartCloudSqlProxy(instance, port, seconds_to_timeout=10):
"""Starts the Cloud SQL Proxy (v1) for instance on the given port.
Args:
instance: The instance to start the proxy for.
port: The port to bind the proxy to.
seconds_to_timeout: Seconds to wait before timing out.
Returns:
The Process object corresponding to the Cloud SQL Proxy.
Raises:
CloudSqlProxyError: An error starting the Cloud SQL Proxy.
SqlProxyNotFound: An error finding a Cloud SQL Proxy installation.
"""
command_path = _GetCloudSqlProxyPath()
# Specify the instance and port to connect with.
args = ['-instances', '{}=tcp:{}'.format(instance.connectionName, port)]
# Specify the credentials.
account = properties.VALUES.core.account.Get(required=True)
args += ['-credential_file', config.Paths().LegacyCredentialsAdcPath(account)]
proxy_args = execution_utils.ArgsForExecutableTool(command_path, *args)
log.status.write(
'Starting Cloud SQL Proxy: [{args}]]\n'.format(args=' '.join(proxy_args))
)
try:
proxy_process = subprocess.Popen(
proxy_args,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except EnvironmentError as e:
if e.errno == errno.ENOENT:
# We are trying to catch here the 'file not found error', so that a proper
# message can be sent to the user with installation help.
raise sql_exceptions.CloudSqlProxyError(
'Failed to start Cloud SQL Proxy. Please make sure it is available in'
' the PATH [{}]. Learn more about installing the Cloud SQL Proxy'
' here:'
' https://cloud.google.com/sql/docs/mysql/connect-auth-proxy#install.'
' If you would like to report this issue, please run the following'
' command: gcloud feedback'.format(command_path)
)
# Else raise the EnvironmentError.
raise
return _WaitForProxyToStart(proxy_process, port, seconds_to_timeout)
def IsInstanceV2(sql_messages, instance):
"""Returns a boolean indicating if the database instance is second gen."""
return instance.backendType == sql_messages.DatabaseInstance.BackendTypeValueValuesEnum.SECOND_GEN
# TODO(b/73648377): Factor out static methods into module-level functions.
class _BaseInstances(object):
"""Common utility functions for sql instances."""
@staticmethod
def GetDatabaseInstances(limit=None, batch_size=None):
"""Gets SQL instances in a given project.
Modifies current state of an individual instance to 'STOPPED' if
activationPolicy is 'NEVER'.
Args:
limit: int, The maximum number of records to yield. None if all available
records should be yielded.
batch_size: int, The number of items to retrieve per request.
Returns:
List of yielded DatabaseInstancePresentation instances.
"""
client = api_util.SqlClient(api_util.API_VERSION_DEFAULT)
sql_client = client.sql_client
sql_messages = client.sql_messages
project_id = properties.VALUES.core.project.Get(required=True)
params = {}
if limit is not None:
params['limit'] = limit
# High default batch size to avoid excess polling on big projects.
default_batch_size = 1000
params['batch_size'] = (
batch_size if batch_size is not None else default_batch_size)
yielded = list_pager.YieldFromList(
sql_client.instances,
sql_messages.SqlInstancesListRequest(project=project_id), **params)
def YieldInstancesWithAModifiedState():
for result in yielded:
yield DatabaseInstancePresentation(result)
return YieldInstancesWithAModifiedState()
@staticmethod
def PrintAndConfirmAuthorizedNetworksOverwrite():
console_io.PromptContinue(
message='When adding a new IP address to authorized networks, '
'make sure to also include any IP addresses that have already been '
'authorized. Otherwise, they will be overwritten and de-authorized.',
default=True,
cancel_on_no=True)
@staticmethod
def PrintAndConfirmSimulatedMaintenanceEvent():
console_io.PromptContinue(
message='This request will trigger a simulated maintenance event '
'and will not change the maintenance version on the instance. Downtime '
'will occur on the instance.',
default=False,
cancel_on_no=True)
@staticmethod
def IsMysqlDatabaseVersion(database_version):
"""Returns a boolean indicating if the database version is MySQL."""
return database_version.name.startswith(_MYSQL_DATABASE_VERSION_PREFIX)
@staticmethod
def IsPostgresDatabaseVersion(database_version):
"""Returns a boolean indicating if the database version is Postgres."""
return database_version.name.startswith(_POSTGRES_DATABASE_VERSION_PREFIX)
@staticmethod
def IsSqlServerDatabaseVersion(database_version):
"""Returns a boolean indicating if the database version is SQL Server."""
return database_version.name.startswith(_SQLSERVER_DATABASE_VERSION_PREFIX)
class InstancesV1Beta4(_BaseInstances):
"""Common utility functions for sql instances V1Beta4."""
@staticmethod
def SetProjectAndInstanceFromRef(instance_resource, instance_ref):
instance_resource.project = instance_ref.project
instance_resource.name = instance_ref.instance
@staticmethod
def AddBackupConfigToSettings(settings, backup_config):
settings.backupConfiguration = backup_config

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*- #
# Copyright 2015 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 utility functions for network operations."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.core.util import times
import ipaddress
import six
IP_VERSION_4 = 4
IP_VERSION_6 = 6
IP_VERSION_UNKNOWN = 0
def GetIpVersion(ip_address):
"""Given an ip address, determine IP version.
Args:
ip_address: string, IP address to test IP version of
Returns:
int, the IP version if it could be determined or IP_VERSION_UNKNOWN
otherwise.
"""
try:
# ipaddress only allows unicode input
version = ipaddress.ip_address(six.text_type(ip_address)).version
if version not in (IP_VERSION_4, IP_VERSION_6):
raise ValueError('Reported IP version not recognized.')
return version
except ValueError: # ipaddr library could not resolve address
return IP_VERSION_UNKNOWN
def GetCurrentTime():
"""Returns the current UTC datetime."""
return times.Now(tzinfo=times.UTC)

View File

@@ -0,0 +1,153 @@
# -*- coding: utf-8 -*- #
# Copyright 2015 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 utility functions for sql operations."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import time
from apitools.base.py import exceptions as base_exceptions
from googlecloudsdk.api_lib.sql import exceptions
from googlecloudsdk.core.console import progress_tracker as console_progress_tracker
from googlecloudsdk.core.util import retry
_MS_PER_SECOND = 1000
# Default timeout for operations.
_OPERATION_TIMEOUT_SECONDS = 600
class _BaseOperations(object):
"""Common utility functions for sql operations."""
_PRE_START_SLEEP_SEC = 1
_INITIAL_SLEEP_MS = 2000
_WAIT_CEILING_MS = 20000
_HTTP_MAX_RETRY_MS = 2000
@classmethod
def WaitForOperation(cls,
sql_client,
operation_ref,
message,
max_wait_seconds=_OPERATION_TIMEOUT_SECONDS):
"""Wait for a Cloud SQL operation to complete.
No operation is done instantly. Wait for it to finish following this logic:
First wait 1s, then query, then retry waiting exponentially more from 2s.
We want to limit to 20s between retries to maintain some responsiveness.
Finally, we want to limit the whole process to a conservative 300s. If we
get to that point it means something is wrong and we can throw an exception.
Args:
sql_client: apitools.BaseApiClient, The client used to make requests.
operation_ref: resources.Resource, A reference for the operation to poll.
message: str, The string to print while polling.
max_wait_seconds: integer or None, the number of seconds before the
poller times out.
Returns:
Operation: The polled operation.
Raises:
OperationError: If the operation has an error code, is in UNKNOWN state,
or if the operation takes more than max_wait_seconds when a value is
specified.
"""
def ShouldRetryFunc(result, state):
# In case of HttpError, retry for up to _HTTP_MAX_RETRY_MS at most.
if isinstance(result, base_exceptions.HttpError):
if state.time_passed_ms > _BaseOperations._HTTP_MAX_RETRY_MS:
raise result
return True
# In case of other Exceptions, raise them immediately.
if isinstance(result, Exception):
raise result
# Otherwise let the retryer do it's job until the Operation is done.
is_operation_done = result.status == sql_client.MESSAGES_MODULE.Operation.StatusValueValuesEnum.DONE
return not is_operation_done
# Set the max wait time.
max_wait_ms = None
if max_wait_seconds:
max_wait_ms = max_wait_seconds * _MS_PER_SECOND
with console_progress_tracker.ProgressTracker(
message, autotick=False) as pt:
time.sleep(_BaseOperations._PRE_START_SLEEP_SEC)
retryer = retry.Retryer(
exponential_sleep_multiplier=2,
max_wait_ms=max_wait_ms,
wait_ceiling_ms=_BaseOperations._WAIT_CEILING_MS)
try:
return retryer.RetryOnResult(
cls.GetOperation, [sql_client, operation_ref],
{'progress_tracker': pt},
should_retry_if=ShouldRetryFunc,
sleep_ms=_BaseOperations._INITIAL_SLEEP_MS)
except retry.WaitException:
raise exceptions.OperationError(
('Operation {0} is taking longer than expected. You can continue '
'waiting for the operation by running `{1}`').format(
operation_ref, cls.GetOperationWaitCommand(operation_ref)))
class OperationsV1Beta4(_BaseOperations):
"""Common utility functions for sql operations V1Beta4."""
@staticmethod
def GetOperation(sql_client, operation_ref, progress_tracker=None):
"""Helper function for getting the status of an operation for V1Beta4 API.
Args:
sql_client: apitools.BaseApiClient, The client used to make requests.
operation_ref: resources.Resource, A reference for the operation to poll.
progress_tracker: progress_tracker.ProgressTracker, A reference for the
progress tracker to tick, in case this function is used in a Retryer.
Returns:
Operation: if the operation succeeded without error or is not yet done.
OperationError: If the operation has an error code or is in UNKNOWN state.
Exception: Any other exception that can occur when calling Get
"""
if progress_tracker:
progress_tracker.Tick()
try:
op = sql_client.operations.Get(
sql_client.MESSAGES_MODULE.SqlOperationsGetRequest(
project=operation_ref.project, operation=operation_ref.operation))
except Exception as e: # pylint:disable=broad-except
# Since we use this function in a retryer.RetryOnResult block, where we
# retry for different exceptions up to different amounts of time, we
# have to catch all exceptions here and return them.
return e
if op.error and op.error.errors:
error_object = op.error.errors[0]
# If there's an error message to show, show it in addition to the code.
error = '[{}]'.format(error_object.code)
if error_object.message:
error += ' ' + error_object.message
return exceptions.OperationError(error)
if op.status == sql_client.MESSAGES_MODULE.Operation.StatusValueValuesEnum.SQL_OPERATION_STATUS_UNSPECIFIED:
return exceptions.OperationError(op.status)
return op
@staticmethod
def GetOperationWaitCommand(operation_ref):
return 'gcloud beta sql operations wait --project {0} {1}'.format(
operation_ref.project, operation_ref.operation)

View File

@@ -0,0 +1,154 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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 command-agnostic utility functions for entraid-certs commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
ACTIVE_CERT_LABEL = 'Active'
NEXT_CERT_LABEL = 'Next'
PREVIOUS_CERT_LABEL = 'Previous'
def ListEntraIdCertificates(sql_client, sql_messages, instance_ref):
"""Calls the list entraid certs endpoint and returns the response."""
return sql_client.instances.ListEntraIdCertificates(
sql_messages.SqlInstancesListEntraIdCertificatesRequest(
project=instance_ref.project, instance=instance_ref.instance
)
)
def _GetCurrentEntraIdCertificate(list_entraid_certs_response):
"""Returns the current Entra ID Cert."""
entraid_cert_types = GetEntraIdCertificateTypeDict(
list_entraid_certs_response
)
return (entraid_cert_types.get(ACTIVE_CERT_LABEL), ACTIVE_CERT_LABEL)
def GetNextEntraIdCertificate(sql_client, sql_messages, instance_ref):
"""Returns the next Entra ID Cert."""
list_entraid_certs_response = ListEntraIdCertificates(
sql_client, sql_messages, instance_ref
)
return _GetNextEntraIdCertificateFromListResponse(list_entraid_certs_response)
def _GetNextEntraIdCertificateFromListResponse(list_entraid_certs_response):
entraid_cert_types = GetEntraIdCertificateTypeDict(
list_entraid_certs_response
)
return (entraid_cert_types.get(NEXT_CERT_LABEL), NEXT_CERT_LABEL)
def GetPreviousEntraIdCertificate(sql_client, sql_messages, instance_ref):
"""Returns the previous Entra ID Cert.
Args:
sql_client: Sql client.
sql_messages: Sql messages.
instance_ref: Instance reference.
Returns:
A tuple of the previous Entra ID Cert and the status of the cert.
"""
list_entraid_certs_response = ListEntraIdCertificates(
sql_client, sql_messages, instance_ref
)
return _GetPreviousEntraIdCertificate(list_entraid_certs_response)
def _GetPreviousEntraIdCertificate(list_entraid_certs_response):
entraid_cert_types = GetEntraIdCertificateTypeDict(
list_entraid_certs_response
)
return (entraid_cert_types.get(PREVIOUS_CERT_LABEL), PREVIOUS_CERT_LABEL)
def GetAddedEntraIdCertificate(sql_client, sql_messages, instance_ref):
"""Returns the added Entra ID Cert.
If this is the first cert, that cert will be Active. Subsequent certs will be
Next.
Args:
sql_client: Sql client.
sql_messages: Sql messages.
instance_ref: Instance reference.
Returns:
A tuple of the added Entra ID Cert and the status of the cert.
"""
list_entraid_certs_response = ListEntraIdCertificates(
sql_client, sql_messages, instance_ref
)
if len(list_entraid_certs_response.certs) == 1:
return _GetCurrentEntraIdCertificate(list_entraid_certs_response)
else:
return _GetNextEntraIdCertificateFromListResponse(
list_entraid_certs_response
)
def GetEntraIdCertificateTypeDict(list_entraid_certs_response):
"""Gets a dictionary mapping Entra ID Cert types to certs.
The keys to the dictionary returned will be some combination of 'Current',
'Next', and 'Previous'.
Args:
list_entraid_certs_response: InstancesListEntraIdCertificatesResponse
instance.
Returns:
A dictionary mapping Entra ID Cert types to SslCert instances.
"""
entraid_cert_types = {}
active_id = list_entraid_certs_response.activeVersion
# Get the active cert.
certs = list_entraid_certs_response.certs
active_cert = None
for cert in certs:
if cert.sha1Fingerprint == active_id:
active_cert = cert
break
if not active_cert:
# No entraid cert types can be discerned; return an empty dict.
return entraid_cert_types
entraid_cert_types[ACTIVE_CERT_LABEL] = active_cert
# Get the inactive certs.
inactive_certs = [cert for cert in certs if cert.sha1Fingerprint != active_id]
if len(inactive_certs) == 1:
inactive_cert = inactive_certs[0]
if inactive_cert.createTime > active_cert.createTime:
# Found the next cert.
entraid_cert_types[NEXT_CERT_LABEL] = inactive_cert
else:
# Found the previous cert.
entraid_cert_types[PREVIOUS_CERT_LABEL] = inactive_cert
elif len(inactive_certs) > 1:
# Sort by expiration date.
inactive_certs = sorted(inactive_certs, key=lambda cert: cert.createTime)
entraid_cert_types[PREVIOUS_CERT_LABEL] = inactive_certs[0]
entraid_cert_types[NEXT_CERT_LABEL] = inactive_certs[-1]
return entraid_cert_types

View File

@@ -0,0 +1,98 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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 command-agnostic utility functions for server-ca-certs commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
ACTIVE_CERT_LABEL = 'Current'
NEXT_CERT_LABEL = 'Next'
PREVIOUS_CERT_LABEL = 'Previous'
def ListServerCas(sql_client, sql_messages, instance_ref):
"""Calls the list server CAs endpoint and returns the response."""
return sql_client.instances.ListServerCas(
sql_messages.SqlInstancesListServerCasRequest(
project=instance_ref.project, instance=instance_ref.instance))
def GetServerCaTypeDict(list_server_cas_response):
"""Gets a dictionary mapping Server CA Cert types to certs.
The keys to the dictionary returned will be some combinatiaon of 'Current',
'Next', and 'Previous'.
Args:
list_server_cas_response: InstancesListServerCasResponse instance.
Returns:
A dictionary mapping Server CA Cert types to SslCert instances.
"""
server_ca_types = {}
active_id = list_server_cas_response.activeVersion
# Get the active cert.
certs = list_server_cas_response.certs
active_cert = None
for cert in certs:
if cert.sha1Fingerprint == active_id:
active_cert = cert
break
if not active_cert:
# No server CA types can be discerned; return an empty dict.
return server_ca_types
server_ca_types[ACTIVE_CERT_LABEL] = active_cert
# Get the inactive certs.
inactive_certs = [cert for cert in certs if cert.sha1Fingerprint != active_id]
if len(inactive_certs) == 1:
inactive_cert = inactive_certs[0]
if inactive_cert.createTime > active_cert.createTime:
# Found the next cert.
server_ca_types[NEXT_CERT_LABEL] = inactive_cert
else:
# Found the previous cert.
server_ca_types[PREVIOUS_CERT_LABEL] = inactive_cert
elif len(inactive_certs) > 1:
# Sort by expiration date.
inactive_certs = sorted(inactive_certs, key=lambda cert: cert.createTime)
server_ca_types[PREVIOUS_CERT_LABEL] = inactive_certs[0]
server_ca_types[NEXT_CERT_LABEL] = inactive_certs[-1]
return server_ca_types
def GetCurrentServerCa(sql_client, sql_messages, instance_ref):
"""Returns the currently active Server CA Cert."""
server_ca_types = GetServerCaTypeDict(
ListServerCas(sql_client, sql_messages, instance_ref))
return server_ca_types.get(ACTIVE_CERT_LABEL)
def GetNextServerCa(sql_client, sql_messages, instance_ref):
"""Returns the upcoming Server CA Cert."""
server_ca_types = GetServerCaTypeDict(
ListServerCas(sql_client, sql_messages, instance_ref))
return server_ca_types.get(NEXT_CERT_LABEL)
def GetPreviousServerCa(sql_client, sql_messages, instance_ref):
"""Returns the previously active Server CA Cert."""
server_ca_types = GetServerCaTypeDict(
ListServerCas(sql_client, sql_messages, instance_ref))
return server_ca_types.get(PREVIOUS_CERT_LABEL)

View File

@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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 command-agnostic utility functions for server-certs commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
ACTIVE_CERT_LABEL = 'Active'
NEXT_CERT_LABEL = 'Next'
PREVIOUS_CERT_LABEL = 'Previous'
def ListServerCertificates(sql_client, sql_messages, instance_ref):
"""Calls the list server certs endpoint and returns the response."""
return sql_client.instances.ListServerCertificates(
sql_messages.SqlInstancesListServerCertificatesRequest(
project=instance_ref.project, instance=instance_ref.instance
)
)
def GetServerCertificateTypeDict(list_server_certs_response):
"""Gets a dictionary mapping Server Cert types to certs.
The keys to the dictionary returned will be some combinatiaon of 'Current',
'Next', and 'Previous'.
Args:
list_server_certs_response: InstancesListServerCertificatesResponse
instance.
Returns:
A dictionary mapping Server Cert types to SslCert instances.
"""
server_cert_types = {}
active_id = list_server_certs_response.activeVersion
# Get the active cert.
certs = list_server_certs_response.serverCerts
active_cert = None
for cert in certs:
if cert.sha1Fingerprint == active_id:
active_cert = cert
break
if not active_cert:
# No server cert types can be discerned; return an empty dict.
return server_cert_types
server_cert_types[ACTIVE_CERT_LABEL] = active_cert
# Get the inactive certs.
inactive_certs = [cert for cert in certs if cert.sha1Fingerprint != active_id]
if len(inactive_certs) == 1:
inactive_cert = inactive_certs[0]
if inactive_cert.createTime > active_cert.createTime:
# Found the next cert.
server_cert_types[NEXT_CERT_LABEL] = inactive_cert
else:
# Found the previous cert.
server_cert_types[PREVIOUS_CERT_LABEL] = inactive_cert
elif len(inactive_certs) > 1:
# Sort by expiration date.
inactive_certs = sorted(inactive_certs, key=lambda cert: cert.createTime)
server_cert_types[PREVIOUS_CERT_LABEL] = inactive_certs[0]
server_cert_types[NEXT_CERT_LABEL] = inactive_certs[-1]
return server_cert_types
def GetCurrentServerCertificate(sql_client, sql_messages, instance_ref):
"""Returns the currently active Server Cert."""
server_cert_types = GetServerCertificateTypeDict(
ListServerCertificates(sql_client, sql_messages, instance_ref)
)
return server_cert_types.get(ACTIVE_CERT_LABEL)
def GetNextServerCertificate(sql_client, sql_messages, instance_ref):
"""Returns the upcoming Server Cert."""
server_cert_types = GetServerCertificateTypeDict(
ListServerCertificates(sql_client, sql_messages, instance_ref)
)
return server_cert_types.get(NEXT_CERT_LABEL)
def GetPreviousServerCertificate(sql_client, sql_messages, instance_ref):
"""Returns the previously active Server Cert."""
server_cert_types = GetServerCertificateTypeDict(
ListServerCertificates(sql_client, sql_messages, instance_ref)
)
return server_cert_types.get(PREVIOUS_CERT_LABEL)

View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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 command-agnostic utility functions for sql users commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
def CreateSetPasswordRequest(sql_messages,
args,
dual_password_type,
project):
"""Creates the set password request to send to UpdateUser.
Args:
sql_messages: module, The messages module that should be used.
args: argparse.Namespace, The arguments that this command was invoked with.
dual_password_type: How we want to interact with the dual password.
project: the project that this user is in
Returns:
CreateSetPasswordRequest
"""
user = sql_messages.User(
project=project,
instance=args.instance,
name=args.username,
host=args.host,
password=args.password)
if dual_password_type:
user.dualPasswordType = dual_password_type
return sql_messages.SqlUsersUpdateRequest(
project=project,
instance=args.instance,
name=args.username,
host=args.host,
user=user)

View File

@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*- #
# Copyright 2015 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 sql utility functions for validating."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.sql import exceptions as sql_exceptions
from googlecloudsdk.api_lib.sql import instances as api_util
from googlecloudsdk.calliope import exceptions
def ValidateInstanceName(instance_name):
if ':' in instance_name:
name_components = instance_name.split(':')
possible_project = name_components[0]
possible_instance = name_components[-1]
raise sql_exceptions.ArgumentError("""\
Instance names cannot contain the ':' character. If you meant to indicate the
project for [{instance}], use only '{instance}' for the argument, and either add
'--project {project}' to the command line or first run
$ gcloud config set project {project}
""".format(project=possible_project, instance=possible_instance))
def ValidateURI(uri, recovery_only):
if (uri is None or not uri) and (not recovery_only):
raise sql_exceptions.ArgumentError("""\
missing URI arg, please include URI arg or set the recovery-only flag if you meant to bring database online only
""")
def ValidateInstanceLocation(args):
"""Construct a Cloud SQL instance from command line args.
Args:
args: argparse.Namespace, The CLI arg namespace.
Raises:
RequiredArgumentException: Zone is required.
ConflictingArgumentsException: Zones in arguments belong to different
regions.
"""
if args.IsSpecified('secondary_zone') and not args.IsSpecified('zone'):
raise exceptions.RequiredArgumentException(
'--zone', '`--zone` is required if --secondary-zone is used '
'while creating an instance.')
if args.IsSpecified('secondary_zone') and args.IsSpecified('zone'):
if args.zone == args.secondary_zone:
raise exceptions.ConflictingArgumentsException(
'Zones in arguments --zone and --secondary-zone are identical.')
region_from_zone = api_util.GetRegionFromZone(args.zone)
region_from_secondary_zone = api_util.GetRegionFromZone(
args.secondary_zone)
if region_from_zone != region_from_secondary_zone:
raise exceptions.ConflictingArgumentsException(
'Zones in arguments --zone and --secondary-zone '
'belong to different regions.')