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,388 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Helpers for the container cluster related commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import json
from googlecloudsdk.api_lib.edge_cloud.container import util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.edge_cloud.container import admin_users
from googlecloudsdk.command_lib.edge_cloud.container import fleet
from googlecloudsdk.command_lib.edge_cloud.container import resource_args
from googlecloudsdk.command_lib.edge_cloud.container import robin
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.core import resources
def GetClusterCreateRequest(args, release_track):
"""Get cluster create request message.
Args:
args: comand line arguments.
release_track: release track of the command.
Returns:
message obj, cluster create request message.
"""
messages = util.GetMessagesModule(release_track)
cluster_ref = GetClusterReference(args)
req = messages.EdgecontainerProjectsLocationsClustersCreateRequest(
cluster=messages.Cluster(),
clusterId=cluster_ref.clustersId,
parent=cluster_ref.Parent().RelativeName(),
)
PopulateClusterMessage(req, messages, args)
if release_track == base.ReleaseTrack.ALPHA:
PopulateClusterAlphaMessage(req, args)
return req
def GetClusterUpgradeRequest(args, release_track):
"""Get cluster upgrade request message.
Args:
args: comand line arguments.
release_track: release track of the command.
Returns:
message obj, cluster upgrade request message.
"""
messages = util.GetMessagesModule(release_track)
cluster_ref = GetClusterReference(args)
upgrade_cluster_req = messages.UpgradeClusterRequest()
upgrade_cluster_req.targetVersion = args.version
if args.schedule.upper() != 'IMMEDIATELY':
raise ValueError('Unsupported --schedule value: ' + args.schedule)
upgrade_cluster_req.schedule = (
messages.UpgradeClusterRequest.ScheduleValueValuesEnum(
args.schedule.upper()
)
)
req = messages.EdgecontainerProjectsLocationsClustersUpgradeRequest()
req.name = cluster_ref.RelativeName()
req.upgradeClusterRequest = upgrade_cluster_req
return req
def PopulateClusterMessage(req, messages, args):
"""Fill the cluster message from command arguments.
Args:
req: create cluster request message.
messages: message module of edgecontainer cluster.
args: command line arguments.
"""
# cluster service IPV4 CIDR blocks have default values.
req.cluster.networking = messages.ClusterNetworking()
req.cluster.networking.clusterIpv4CidrBlocks = [args.cluster_ipv4_cidr]
req.cluster.networking.servicesIpv4CidrBlocks = [args.services_ipv4_cidr]
if flags.FlagIsExplicitlySet(args, 'default_max_pods_per_node'):
req.cluster.defaultMaxPodsPerNode = int(args.default_max_pods_per_node)
if flags.FlagIsExplicitlySet(args, 'labels'):
req.cluster.labels = messages.Cluster.LabelsValue()
req.cluster.labels.additionalProperties = []
for key, value in args.labels.items():
v = messages.Cluster.LabelsValue.AdditionalProperty()
v.key = key
v.value = value
req.cluster.labels.additionalProperties.append(v)
if (
flags.FlagIsExplicitlySet(args, 'maintenance_window_recurrence')
or flags.FlagIsExplicitlySet(args, 'maintenance_window_start')
or flags.FlagIsExplicitlySet(args, 'maintenance_window_end')
):
req.cluster.maintenancePolicy = messages.MaintenancePolicy()
req.cluster.maintenancePolicy.window = messages.MaintenanceWindow()
req.cluster.maintenancePolicy.window.recurringWindow = (
messages.RecurringTimeWindow()
)
if flags.FlagIsExplicitlySet(args, 'maintenance_window_recurrence'):
req.cluster.maintenancePolicy.window.recurringWindow.recurrence = (
args.maintenance_window_recurrence
)
req.cluster.maintenancePolicy.window.recurringWindow.window = (
messages.TimeWindow()
)
if flags.FlagIsExplicitlySet(args, 'maintenance_window_start'):
req.cluster.maintenancePolicy.window.recurringWindow.window.startTime = (
args.maintenance_window_start
)
if flags.FlagIsExplicitlySet(args, 'maintenance_window_end'):
req.cluster.maintenancePolicy.window.recurringWindow.window.endTime = (
args.maintenance_window_end
)
if flags.FlagIsExplicitlySet(args, 'control_plane_kms_key'):
req.cluster.controlPlaneEncryption = messages.ControlPlaneEncryption()
req.cluster.controlPlaneEncryption.kmsKey = args.control_plane_kms_key
if flags.FlagIsExplicitlySet(args, 'zone_storage_kms_key'):
req.cluster.zoneStorageEncryption = messages.ZoneStorageEncryption()
req.cluster.zoneStorageEncryption.kmsKey = args.zone_storage_kms_key
admin_users.SetAdminUsers(messages, args, req)
fleet.SetFleetProjectPath(GetClusterReference(args), args, req)
if flags.FlagIsExplicitlySet(args, 'external_lb_ipv4_address_pools'):
req.cluster.externalLoadBalancerIpv4AddressPools = (
args.external_lb_ipv4_address_pools
)
if flags.FlagIsExplicitlySet(args, 'version'):
req.cluster.targetVersion = args.version
if flags.FlagIsExplicitlySet(args, 'release_channel'):
req.cluster.releaseChannel = messages.Cluster.ReleaseChannelValueValuesEnum(
args.release_channel.upper()
)
if (
flags.FlagIsExplicitlySet(args, 'control_plane_node_location')
or flags.FlagIsExplicitlySet(args, 'control_plane_node_count')
or flags.FlagIsExplicitlySet(args, 'control_plane_machine_filter')
):
# creating an LCP cluster.
req.cluster.controlPlane = messages.ControlPlane()
req.cluster.controlPlane.local = messages.Local()
if flags.FlagIsExplicitlySet(args, 'control_plane_node_location'):
req.cluster.controlPlane.local.nodeLocation = (
args.control_plane_node_location
)
if flags.FlagIsExplicitlySet(args, 'control_plane_node_count'):
req.cluster.controlPlane.local.nodeCount = int(
args.control_plane_node_count
)
if flags.FlagIsExplicitlySet(args, 'control_plane_machine_filter'):
req.cluster.controlPlane.local.machineFilter = (
args.control_plane_machine_filter
)
if flags.FlagIsExplicitlySet(
args, 'control_plane_shared_deployment_policy'
):
req.cluster.controlPlane.local.sharedDeploymentPolicy = (
messages.Local.SharedDeploymentPolicyValueValuesEnum(
args.control_plane_shared_deployment_policy.upper()
)
)
if flags.FlagIsExplicitlySet(args, 'offline_reboot_ttl'):
if not req.cluster.survivabilityConfig:
req.cluster.survivabilityConfig = messages.SurvivabilityConfig()
req.cluster.survivabilityConfig.offlineRebootTtl = (
json.dumps(args.offline_reboot_ttl) + 's'
)
if flags.FlagIsExplicitlySet(args, 'control_plane_node_storage_schema'):
req.cluster.controlPlane.local.controlPlaneNodeStorageSchema = (
args.control_plane_node_storage_schema
)
SetContainerRuntimeConfig(req, args, messages)
EnableGoogleGroupAuthentication(req, args, messages)
def PopulateClusterAlphaMessage(req, args):
"""Filled the Alpha cluster message from command arguments.
Args:
req: create cluster request message.
args: command line arguments.
"""
if flags.FlagIsExplicitlySet(args, 'cluster_ipv6_cidr'):
req.cluster.networking.clusterIpv6CidrBlocks = [args.cluster_ipv6_cidr]
if flags.FlagIsExplicitlySet(args, 'services_ipv6_cidr'):
req.cluster.networking.servicesIpv6CidrBlocks = [args.services_ipv6_cidr]
if flags.FlagIsExplicitlySet(args, 'external_lb_ipv6_address_pools'):
req.cluster.externalLoadBalancerIpv6AddressPools = (
args.external_lb_ipv6_address_pools
)
resource_args.SetSystemAddonsConfig(args, req)
resource_args.SetExternalLoadBalancerAddressPoolsConfig(args, req)
EnableClusterIsolationConfig(req, args)
EnableRemoteBackupConfig(req, args)
if flags.FlagIsExplicitlySet(args, 'enable_robin_cns'):
robin.EnableRobinCNSInRequest(req, args)
messages = util.GetMessagesModule(base.ReleaseTrack.ALPHA)
if flags.FlagIsExplicitlySet(
args, 'control_plane_node_system_partition_size_gib'
):
SetControlPlaneNodeSystemPartitionSize(req, args, messages)
def IsLCPCluster(args):
"""Identify if the command is creating LCP cluster.
Args:
args: command line arguments.
Returns:
Boolean, indication of LCP cluster.
"""
if (
flags.FlagIsExplicitlySet(args, 'control_plane_node_location')
and flags.FlagIsExplicitlySet(args, 'control_plane_node_count')
and (
flags.FlagIsExplicitlySet(args, 'external_lb_ipv4_address_pools')
or flags.FlagIsExplicitlySet(args, 'external_lb_address_pools')
)
):
return True
return False
def IsOfflineCredential(args):
"""Identify if the command is requesting an offline credential for LCP cluster.
Args:
args: command line arguments.
Returns:
Boolean, indication of requesting offline credential.
"""
if flags.FlagIsExplicitlySet(args, 'offline_credential'):
return True
return False
def GetClusterReference(args):
"""Get edgecontainer cluster resources.
Args:
args: command line arguments.
Returns:
edgecontainer cluster resources.
"""
return resources.REGISTRY.ParseRelativeName(
args.CONCEPTS.cluster.Parse().RelativeName(),
collection='edgecontainer.projects.locations.clusters',
)
def ValidateClusterCreateRequest(req, release_track):
"""Validate cluster create request message.
Args:
req: Create cluster request message.
release_track: Release track of the command.
Returns:
Single string of error message.
"""
messages = util.GetMessagesModule(release_track)
if (
req.cluster.releaseChannel
== messages.Cluster.ReleaseChannelValueValuesEnum.REGULAR
and req.cluster.targetVersion is not None
):
return (
'Invalid Argument: REGULAR release channel does not support'
' specification of version'
)
return None
def SetContainerRuntimeConfig(req, args, messages):
"""Set container runtime config in the cluster request message.
Args:
req: Create cluster request message.
args: Command line arguments.
messages: Message module of edgecontainer cluster.
"""
if flags.FlagIsExplicitlySet(args, 'container_default_runtime_class'):
req.cluster.containerRuntimeConfig = messages.ContainerRuntimeConfig()
if args.container_default_runtime_class.upper() == 'GVISOR':
req.cluster.containerRuntimeConfig.defaultContainerRuntime = (
messages.ContainerRuntimeConfig.DefaultContainerRuntimeValueValuesEnum.GVISOR
)
elif args.container_default_runtime_class.upper() == 'RUNC':
req.cluster.containerRuntimeConfig.defaultContainerRuntime = (
messages.ContainerRuntimeConfig.DefaultContainerRuntimeValueValuesEnum.RUNC
)
else:
raise ValueError(
'Unsupported --container_default_runtime_class value: '
+ args.container_default_runtime_class
)
def EnableClusterIsolationConfig(req, args):
"""Set secure cluster isolation config in the cluster request message.
Args:
req: Create cluster request message.
args: Command line arguments.
"""
if flags.FlagIsExplicitlySet(args, 'enable_cluster_isolation'):
if args.enable_cluster_isolation.upper() == 'TRUE':
req.cluster.enableClusterIsolation = True
elif args.enable_cluster_isolation.upper() == 'FALSE':
req.cluster.enableClusterIsolation = False
else:
raise ValueError(
'Unsupported --enable_cluster_isolation value: '
+ args.enable_cluster_isolation
)
def EnableGoogleGroupAuthentication(req, args, messages):
"""Set Google Group authentication config in the cluster request message.
Args:
req: Create cluster request message.
args: Command line arguments.
messages: Message module of edgecontainer cluster.
"""
if flags.FlagIsExplicitlySet(args, 'enable_google_group_authentication'):
req.cluster.googleGroupAuthentication = (
messages.GoogleGroupAuthenticationConfig()
)
req.cluster.googleGroupAuthentication.enable = (
args.enable_google_group_authentication)
def EnableRemoteBackupConfig(req, args):
"""Set remote backup config in the cluster request message.
Args:
req: Create cluster request message.
args: Command line arguments.
"""
if flags.FlagIsExplicitlySet(args, 'enable_remote_backup'):
req.cluster.enableRemoteBackup = args.enable_remote_backup
def SetControlPlaneNodeSystemPartitionSize(req, args, messages):
"""Set control plane node system partition size in the cluster request message.
Args:
req: Create cluster request message.
args: Command line arguments.
messages: Message module of edgecontainer cluster.
"""
if args.control_plane_node_system_partition_size_gib == 100:
req.cluster.controlPlane.local.controlPlaneNodeSystemPartitionSize = (
messages.Local.ControlPlaneNodeSystemPartitionSizeValueValuesEnum.SYSTEM_PARTITION_GIB_SIZE100
)
elif args.control_plane_node_system_partition_size_gib == 300:
req.cluster.controlPlane.local.controlPlaneNodeSystemPartitionSize = (
messages.Local.ControlPlaneNodeSystemPartitionSizeValueValuesEnum.SYSTEM_PARTITION_GIB_SIZE300
)
else:
raise ValueError(
'Unsupported --control_plane_node_system_partition_size_gib value: '
+ args.control_plane_node_system_partition_size_gib
+ '; valid values are 100 and 300.'
)

View File

@@ -0,0 +1,259 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Helpers for the container node pool related commands."""
from googlecloudsdk.api_lib.edge_cloud.container import util
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.util.args import labels_util
from googlecloudsdk.core import resources
def GetNodePoolReference(args):
"""Get edgecontainer node pool resources.
Args:
args: command line arguments.
Returns:
edgecontainer node pool resources.
"""
return resources.REGISTRY.ParseRelativeName(
args.CONCEPTS.node_pool.Parse().RelativeName(),
collection='edgecontainer.projects.locations.clusters.nodePools',
)
def GetNodePoolGetRequest(args, release_track):
"""Get node pool get request message.
Args:
args: comand line arguments.
release_track: release track of the command.
Returns:
message obj, node pool get request message.
"""
messages = util.GetMessagesModule(release_track)
req = messages.EdgecontainerProjectsLocationsClustersNodePoolsGetRequest(
name=args.CONCEPTS.node_pool.Parse().RelativeName(),
)
return req
def GetNodePoolCreateRequest(args, release_track):
"""Get node pool create request message.
Args:
args: comand line arguments.
release_track: release track of the command.
Returns:
message obj, node pool create request message.
"""
messages = util.GetMessagesModule(release_track)
node_pool_ref = GetNodePoolReference(args)
req = messages.EdgecontainerProjectsLocationsClustersNodePoolsCreateRequest(
nodePool=messages.NodePool(),
nodePoolId=node_pool_ref.nodePoolsId,
parent=node_pool_ref.Parent().RelativeName(),
)
PopulateNodePoolCreateMessage(req, messages, args)
if release_track == base.ReleaseTrack.ALPHA:
PopulateNodePoolAlphaCreateMessage(req, args)
return req
def GetNodePoolUpdateRequest(args, release_track, existing_node_pool):
"""Get node pool update request message.
Args:
args: comand line arguments.
release_track: release track of the command.
existing_node_pool: existing node pool.
Returns:
message obj, node pool update request message.
"""
messages = util.GetMessagesModule(release_track)
req = messages.EdgecontainerProjectsLocationsClustersNodePoolsPatchRequest(
name=args.CONCEPTS.node_pool.Parse().RelativeName(),
nodePool=messages.NodePool(),
)
update_mask_pieces = []
PopulateNodePoolUpdateMessage(
req, messages, args, update_mask_pieces, existing_node_pool
)
req.updateMask = ','.join(update_mask_pieces)
return req
def PopulateNodePoolCreateMessage(req, messages, args):
"""Fill the node pool message from command arguments.
Args:
req: create node pool request message.
messages: message module of edgecontainer node pool.
args: command line arguments.
"""
req.nodePool.nodeCount = int(args.node_count)
req.nodePool.nodeLocation = args.node_location
if flags.FlagIsExplicitlySet(args, 'machine_filter'):
req.nodePool.machineFilter = args.machine_filter
if flags.FlagIsExplicitlySet(args, 'local_disk_kms_key'):
req.nodePool.localDiskEncryption = messages.LocalDiskEncryption()
req.nodePool.localDiskEncryption.kmsKey = args.local_disk_kms_key
if flags.FlagIsExplicitlySet(args, 'labels'):
req.nodePool.labels = messages.NodePool.LabelsValue()
req.nodePool.labels.additionalProperties = []
for key, value in args.labels.items():
v = messages.NodePool.LabelsValue.AdditionalProperty()
v.key = key
v.value = value
req.nodePool.labels.additionalProperties.append(v)
if flags.FlagIsExplicitlySet(args, 'node_labels'):
req.nodePool.nodeConfig = messages.NodeConfig()
req.nodePool.nodeConfig.labels = messages.NodeConfig.LabelsValue()
req.nodePool.nodeConfig.labels.additionalProperties = []
for key, value in args.node_labels.items():
v = messages.NodeConfig.LabelsValue.AdditionalProperty()
v.key = key
v.value = value
req.nodePool.nodeConfig.labels.additionalProperties.append(v)
if flags.FlagIsExplicitlySet(args, 'node_storage_schema'):
if not req.nodePool.nodeConfig:
req.nodePool.nodeConfig = messages.NodeConfig()
req.nodePool.nodeConfig.nodeStorageSchema = args.node_storage_schema
def PopulateNodePoolAlphaCreateMessage(req, args):
"""Fill the Alpha create node pool message from command arguments.
Args:
req: create node pool request message.
args: command line arguments.
"""
if flags.FlagIsExplicitlySet(args, 'node_system_partition_size_gib'):
messages = util.GetMessagesModule(base.ReleaseTrack.ALPHA)
if not req.nodePool.nodeConfig:
req.nodePool.nodeConfig = messages.NodeConfig()
SetNodeSystemPartitionSize(req, args, messages)
def PopulateNodePoolUpdateAlphaMessage(req, messages, update_mask_pieces, args):
"""Filled the Alpha node pool message from command arguments.
Args:
req: create node pool request message.
messages: message module of edgecontainer node pool.
update_mask_pieces: update masks.
args: command line arguments.
"""
if flags.FlagIsExplicitlySet(
args, 'use_google_managed_key'
) and flags.FlagIsExplicitlySet(args, 'local_disk_kms_key'):
raise exceptions.InvalidArgumentException(
'--use-google-managed-key, --local-disk-kms-key',
'cannot be specified at the same time',
)
if flags.FlagIsExplicitlySet(args, 'use_google_managed_key'):
update_mask_pieces.append('localDiskEncryption')
req.nodePool.localDiskEncryption = messages.LocalDiskEncryption()
req.nodePool.localDiskEncryption.kmsKey = ''
return
if flags.FlagIsExplicitlySet(args, 'local_disk_kms_key'):
update_mask_pieces.append('localDiskEncryption')
req.nodePool.localDiskEncryption = messages.LocalDiskEncryption()
req.nodePool.localDiskEncryption.kmsKey = args.local_disk_kms_key
return
def PopulateNodePoolUpdateMessage(
req, messages, args, update_mask_pieces, existing_node_pool
):
"""Fill the node pool message from command arguments.
Args:
req: update node pool request message.
messages: message module of edgecontainer node pool.
args: command line arguments.
update_mask_pieces: update mask pieces.
existing_node_pool: existing node pool.
"""
if flags.FlagIsExplicitlySet(args, 'machine_filter'):
update_mask_pieces.append('machineFilter')
req.nodePool.machineFilter = args.machine_filter
if flags.FlagIsExplicitlySet(args, 'node_count'):
update_mask_pieces.append('nodeCount')
req.nodePool.nodeCount = int(args.node_count)
add_labels = labels_util.GetUpdateLabelsDictFromArgs(args)
remove_labels = labels_util.GetRemoveLabelsListFromArgs(args)
value_type = messages.NodePool.LabelsValue
label_update_result = labels_util.Diff(
additions=add_labels, subtractions=remove_labels, clear=args.clear_labels
).Apply(value_type, existing_node_pool.labels)
if label_update_result.needs_update:
update_mask_pieces.append('labels')
req.nodePool.labels = label_update_result.labels
if flags.FlagIsExplicitlySet(args, 'node_labels'):
update_mask_pieces.append('nodeConfig.labels')
req.nodePool.nodeConfig = messages.NodeConfig()
req.nodePool.nodeConfig.labels = messages.NodeConfig.LabelsValue()
req.nodePool.nodeConfig.labels.additionalProperties = []
for key, value in args.node_labels.items():
v = messages.NodeConfig.LabelsValue.AdditionalProperty()
v.key = key
v.value = value
req.nodePool.nodeConfig.labels.additionalProperties.append(v)
if flags.FlagIsExplicitlySet(
args, 'use_google_managed_key'
) and flags.FlagIsExplicitlySet(args, 'local_disk_kms_key'):
raise exceptions.InvalidArgumentException(
'--use-google-managed-key, --local-disk-kms-key',
'cannot be specified at the same time',
)
if flags.FlagIsExplicitlySet(args, 'use_google_managed_key'):
update_mask_pieces.append('localDiskEncryption')
req.nodePool.localDiskEncryption = messages.LocalDiskEncryption()
req.nodePool.localDiskEncryption.kmsKey = ''
if flags.FlagIsExplicitlySet(args, 'local_disk_kms_key'):
update_mask_pieces.append('localDiskEncryption')
req.nodePool.localDiskEncryption = messages.LocalDiskEncryption()
req.nodePool.localDiskEncryption.kmsKey = args.local_disk_kms_key
def SetNodeSystemPartitionSize(req, args, messages):
"""Set node system partition size in the node pool request message.
Args:
req: Create node pool request message.
args: Command line arguments.
messages: Message module of edgecontainer node pool.
"""
if args.node_system_partition_size_gib == 100:
req.nodePool.nodeConfig.nodeSystemPartitionSize = (
messages.NodeConfig.NodeSystemPartitionSizeValueValuesEnum.SYSTEM_PARTITION_GIB_SIZE100
)
elif args.node_system_partition_size_gib == 300:
req.nodePool.nodeConfig.nodeSystemPartitionSize = (
messages.NodeConfig.NodeSystemPartitionSizeValueValuesEnum.SYSTEM_PARTITION_GIB_SIZE300
)
else:
raise ValueError(
'Unsupported --node_system_partition_size_gib value: '
+ args.node_system_partition_size_gib
+ '; valid values are 100 and 300.'
)

View File

@@ -0,0 +1,55 @@
# -*- 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.
"""Edge Container API utilities."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import base
VERSION_MAP = {
base.ReleaseTrack.ALPHA: 'v1alpha',
base.ReleaseTrack.BETA: 'v1beta',
base.ReleaseTrack.GA: 'v1',
}
def GetMessagesModule(release_track=base.ReleaseTrack.GA):
api_version = VERSION_MAP.get(release_track)
return apis.GetMessagesModule('edgecontainer', api_version)
def GetClientInstance(release_track=base.ReleaseTrack.GA):
api_version = VERSION_MAP.get(release_track)
return apis.GetClientInstance('edgecontainer', api_version)
class OperationPoller(waiter.CloudOperationPoller):
"""An implementation of a operation poller."""
def GetResult(self, operation):
"""Overrides.
Args:
operation: api_name_messages.Operation.
Returns:
result of result_service.Get request.
"""
return operation

View File

@@ -0,0 +1,36 @@
# -*- 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.
"""Distributed Cloud Edge Network interconnects API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.edge_cloud.networking import utils
class InterconnectsClient(object):
"""Client for private connections service in the API."""
def __init__(self, release_track, client=None, messages=None):
self._client = client or utils.GetClientInstance(release_track)
self._messages = messages or utils.GetMessagesModule(release_track)
self._service = self._client.projects_locations_zones_interconnects
def GetStatus(self, interconnect_ref):
"""Get the status of a specified interconnect."""
get_interconnect_status_req = self._messages.EdgenetworkProjectsLocationsZonesInterconnectsDiagnoseRequest(
name=interconnect_ref.RelativeName())
return self._service.Diagnose(get_interconnect_status_req)

View File

@@ -0,0 +1,36 @@
# -*- 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.
"""Distributed Cloud Edge Network network API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.edge_cloud.networking import utils
class NetworksClient(object):
"""Client for network resource of GDCE fabric API."""
def __init__(self, release_track, client=None, messages=None):
self._client = client or utils.GetClientInstance(release_track)
self._messages = messages or utils.GetMessagesModule(release_track)
self._service = self._client.projects_locations_zones_networks
def GetStatus(self, network_ref):
"""Get the status of a specified network."""
get_network_status_req = self._messages.EdgenetworkProjectsLocationsZonesNetworksDiagnoseRequest(
name=network_ref.RelativeName())
return self._service.Diagnose(get_network_status_req)

View File

@@ -0,0 +1,293 @@
# -*- 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.
"""Distributed Cloud Edge Network router API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import ipaddress
from apitools.base.py import encoding
from googlecloudsdk.api_lib.edge_cloud.networking import utils
from googlecloudsdk.calliope import parser_errors
from googlecloudsdk.core import exceptions as core_exceptions
import six
class RoutersClient(object):
"""Client for private connections service in the API."""
# REST API Field Names for the updateMask
FIELD_PATH_INTERFACE = 'interface'
FIELD_PATH_BGP_PEER = 'bgp_peer'
FIELD_PATH_ROUTE_ADVERTISEMENTS = 'route_advertisements'
def __init__(self, release_track, client=None, messages=None):
self._client = client or utils.GetClientInstance(release_track)
self._messages = messages or utils.GetMessagesModule(release_track)
self._service = self._client.projects_locations_zones_routers
self._resource_parser = utils.GetResourceParser(release_track)
def WaitForOperation(self, operation):
"""Waits for the given google.longrunning.Operation to complete."""
return utils.WaitForOperation(self._client, operation, self._service)
def ModifyToAddInterface(self, router_ref, args, existing):
"""Mutate the router to add an interface."""
replacement = encoding.CopyProtoMessage(existing)
new_interface = self._messages.Interface(name=args.interface_name)
if args.interconnect_attachment is not None:
attachment_ref = self._resource_parser.Create(
'edgenetwork.projects.locations.zones.interconnectAttachments',
interconnectAttachmentsId=args.interconnect_attachment,
projectsId=router_ref.projectsId,
locationsId=router_ref.locationsId,
zonesId=router_ref.zonesId)
if args.ip_mask_length is None or args.ip_address is None:
raise parser_errors.ArgumentException(
'--ip-address and --ip-mask-length must be set'
)
try:
ip_address = ipaddress.ip_address(args.ip_address)
except ValueError as err:
raise parser_errors.ArgumentException(str(err))
if args.ip_mask_length > ip_address.max_prefixlen:
raise parser_errors.ArgumentException(
'--ip-mask-length should be less than %s' % ip_address.max_prefixlen
)
cidr = '{0}/{1}'.format(args.ip_address, args.ip_mask_length)
if ip_address.version == 4: # Is an ipv4 cidr
new_interface.ipv4Cidr = cidr
else:
new_interface.ipv6Cidr = cidr
new_interface.linkedInterconnectAttachment = attachment_ref.RelativeName()
if args.subnetwork is not None:
subnet_ref = self._resource_parser.Create(
'edgenetwork.projects.locations.zones.subnets',
subnetsId=args.subnetwork,
projectsId=router_ref.projectsId,
locationsId=router_ref.locationsId,
zonesId=router_ref.zonesId)
new_interface.subnetwork = subnet_ref.RelativeName()
if args.loopback_ip_addresses is not None:
new_interface.loopbackIpAddresses = args.loopback_ip_addresses
replacement.interface.append(new_interface)
return replacement
def ModifyToRemoveInterface(self, args, existing):
"""Mutate the router to delete a list of interfaces."""
# Get the list of interfaces that are to be removed from args.
input_remove_list = args.interface_names if args.interface_names else []
input_remove_list = input_remove_list + ([args.interface_name]
if args.interface_name else [])
# Remove interface if exists
actual_remove_list = []
replacement = encoding.CopyProtoMessage(existing)
existing_router = encoding.CopyProtoMessage(existing)
for iface in existing_router.interface:
if iface.name in input_remove_list:
replacement.interface.remove(iface)
actual_remove_list.append(iface.name)
# If there still are interfaces that we didn't find, the input is invalid.
not_found_interface = sorted(
set(input_remove_list) - set(actual_remove_list))
if not_found_interface:
error_msg = 'interface [{}] not found'.format(
', '.join(not_found_interface))
raise core_exceptions.Error(error_msg)
return replacement
def ModifyToAddBgpPeer(self, args, existing):
"""Mutate the router to add a BGP peer."""
replacement = encoding.CopyProtoMessage(existing)
bgp_peer_args = {
'name': args.peer_name,
'interface': args.interface,
'peerAsn': args.peer_asn,
}
if args.peer_ipv4_range is not None:
bgp_peer_args['peerIpv4Cidr'] = args.peer_ipv4_range
# Only present in ALPHA release
if 'peer_ipv6_range' in args and args.peer_ipv6_range is not None:
bgp_peer_args['peerIpv6Cidr'] = args.peer_ipv6_range
new_bgp_peer = self._messages.BgpPeer(**bgp_peer_args)
replacement.bgpPeer.append(new_bgp_peer)
return replacement
def ModifyToRemoveBgpPeer(self, args, existing):
"""Mutate the router to delete BGP peers."""
input_remove_list = args.peer_names if args.peer_names else []
input_remove_list = input_remove_list + ([args.peer_name]
if args.peer_name else [])
actual_remove_list = []
replacement = encoding.CopyProtoMessage(existing)
existing_router = encoding.CopyProtoMessage(existing)
for peer in existing_router.bgpPeer:
if peer.name in input_remove_list:
replacement.bgpPeer.remove(peer)
actual_remove_list.append(peer.name)
# If there still are bgp peers that we didn't find, the input is invalid.
not_found_peer = sorted(set(input_remove_list) - set(actual_remove_list))
if not_found_peer:
error_msg = 'peer [{}] not found'.format(', '.join(not_found_peer))
raise core_exceptions.Error(error_msg)
return replacement
def AddInterface(self, router_ref, args):
"""Create an interface on a router."""
# Get current interfaces of router
get_router_req = self._messages.EdgenetworkProjectsLocationsZonesRoutersGetRequest(
name=router_ref.RelativeName())
router_object = self._service.Get(get_router_req)
# Update interfaces to add the new interface
new_router_object = self.ModifyToAddInterface(router_ref, args,
router_object)
update_router_req = self._messages.EdgenetworkProjectsLocationsZonesRoutersPatchRequest(
name=router_ref.RelativeName(),
router=new_router_object,
updateMask=self.FIELD_PATH_INTERFACE)
return self._service.Patch(update_router_req)
def RemoveInterface(self, router_ref, args):
"""Remove a list of interfaces on a router."""
# Get current interfaces of router
get_router_req = self._messages.EdgenetworkProjectsLocationsZonesRoutersGetRequest(
name=router_ref.RelativeName())
router_object = self._service.Get(get_router_req)
# Update interfaces to add the new interface
new_router_object = self.ModifyToRemoveInterface(args, router_object)
update_router_req = self._messages.EdgenetworkProjectsLocationsZonesRoutersPatchRequest(
name=router_ref.RelativeName(),
router=new_router_object,
updateMask=self.FIELD_PATH_INTERFACE)
return self._service.Patch(update_router_req)
def ModifyToApplyAdvertisementChanges(self, args, existing):
"""Create a router based on `existing` with the routes change."""
def cidrset(cidr_strs):
return set(ipaddress.ip_network(cidrstr) for cidrstr in cidr_strs)
def sorted_strings(cidrs):
return [six.text_type(cidr) for cidr in sorted(cidrs)]
advertisements = cidrset(existing.routeAdvertisements)
replacement = encoding.CopyProtoMessage(existing)
if args.add_advertisement_ranges:
to_add = set(args.add_advertisement_ranges)
already_present = sorted_strings(advertisements & to_add)
if already_present:
raise core_exceptions.Error(
'attempting to add routes that are already present: {}'.format(
', '.join(already_present)))
advertisements |= to_add
elif args.remove_advertisement_ranges:
to_rm = cidrset(args.remove_advertisement_ranges)
already_missing = sorted_strings(to_rm - advertisements)
if already_missing:
raise core_exceptions.Error(
'attempting to remove routes that are not present: {}'.format(
', '.join(already_missing)))
advertisements -= to_rm
elif args.set_advertisement_ranges:
advertisements = cidrset(args.set_advertisement_ranges)
else:
raise parser_errors.ArgumentException(
'Missing --add-advertisement-ranges, '
'--remove-advertisement-ranges, or --set-advertisement-ranges')
replacement.routeAdvertisements = list(map(str, sorted(advertisements)))
return replacement
def ChangeAdvertisements(self, router_ref, args):
"""Create a patch request that updates the Route advertisements of a router.
"""
get_router_req = self._messages.EdgenetworkProjectsLocationsZonesRoutersGetRequest(
name=router_ref.RelativeName())
router_object = self._service.Get(get_router_req)
new_router_object = self.ModifyToApplyAdvertisementChanges(
args, router_object)
update_router_request = (
self._messages.EdgenetworkProjectsLocationsZonesRoutersPatchRequest(
name=router_ref.RelativeName(),
router=new_router_object,
updateMask=self.FIELD_PATH_ROUTE_ADVERTISEMENTS))
return self._service.Patch(update_router_request)
def AddBgpPeer(self, router_ref, args):
"""Mutate the router so to add a BGP peer."""
# Get current router
get_router_req = self._messages.EdgenetworkProjectsLocationsZonesRoutersGetRequest(
name=router_ref.RelativeName())
router_object = self._service.Get(get_router_req)
# Update router object to add the new bgp peer
new_router_object = self.ModifyToAddBgpPeer(args, router_object)
update_router_req = self._messages.EdgenetworkProjectsLocationsZonesRoutersPatchRequest(
name=router_ref.RelativeName(),
router=new_router_object,
updateMask=self.FIELD_PATH_BGP_PEER)
return self._service.Patch(update_router_req)
def RemoveBgpPeer(self, router_ref, args):
"""Mutate the router so to remove a BGP peer."""
# Get current router
get_router_req = self._messages.EdgenetworkProjectsLocationsZonesRoutersGetRequest(
name=router_ref.RelativeName())
router_object = self._service.Get(get_router_req)
# Update router object to remove specified bgp peers
new_router_object = self.ModifyToRemoveBgpPeer(args, router_object)
update_router_req = self._messages.EdgenetworkProjectsLocationsZonesRoutersPatchRequest(
name=router_ref.RelativeName(),
router=new_router_object,
updateMask=self.FIELD_PATH_BGP_PEER)
return self._service.Patch(update_router_req)
def GetStatus(self, router_ref):
"""Get the status of a specified router."""
get_router_status_req = self._messages.EdgenetworkProjectsLocationsZonesRoutersDiagnoseRequest(
name=router_ref.RelativeName())
return self._service.Diagnose(get_router_status_req)

View File

@@ -0,0 +1,107 @@
# -*- 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.
"""Utility functions for Distributed Cloud Edge Network."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import ipaddress
import re
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.core import resources
VERSION_MAP = {
base.ReleaseTrack.ALPHA: 'v1alpha1',
base.ReleaseTrack.GA: 'v1',
}
def GetClientInstance(release_track=base.ReleaseTrack.GA, no_http=False):
api_version = VERSION_MAP.get(release_track)
return apis.GetClientInstance('edgenetwork', api_version, no_http=no_http)
def GetMessagesModule(release_track=base.ReleaseTrack.GA):
api_version = VERSION_MAP.get(release_track)
return apis.GetMessagesModule('edgenetwork', api_version)
def GetResourceParser(release_track=base.ReleaseTrack.GA):
resource_parser = resources.Registry()
api_version = VERSION_MAP.get(release_track)
resource_parser.RegisterApiByName('edgenetwork', api_version)
return resource_parser
def WaitForOperation(client, operation, resource):
"""Waits for the given google.longrunning.Operation to complete."""
operation_ref = resources.REGISTRY.Parse(
operation.name, collection='edgenetwork.projects.locations.operations')
poller = waiter.CloudOperationPoller(resource,
client.projects_locations_operations)
waiter.WaitFor(
poller, operation_ref,
'Waiting for [{0}] to finish'.format(operation_ref.RelativeName()))
def IsValidIPV4(ip):
"""Accepts an ipv4 address in string form and returns True if valid."""
match = re.match(r'^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$', ip)
if not match:
return False
octets = [int(x) for x in match.groups()]
# first octet must not be 0
if octets[0] == 0:
return False
for n in octets:
if n < 0 or n > 255:
return False
return True
def IsValidIPV6(ip):
"""Validates a given ip address to be IPv6 address."""
try:
_ = ipaddress.IPv6Address(ip)
except ValueError:
return False
return True
def IPArgument(value):
"""Argparse argument type that checks for a valid ipv4 address."""
if not IsValidIPV4(value) and not IsValidIPV6(value):
raise arg_parsers.ArgumentTypeError(
"invalid IPv4 or IPv6 address: '{0}'".format(value)
)
return value
def IPV4Argument(value):
"""Argparse argument type that checks for a valid ipv4 address."""
if not IsValidIPV4(value):
raise arg_parsers.ArgumentTypeError(
"invalid ipv4 value: '{0}'".format(value))
return value

View File

@@ -0,0 +1,36 @@
# -*- 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.
"""Distributed Cloud Edge Network zone API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.edge_cloud.networking import utils
class ZonesClient(object):
"""Client for zone resource of GDCE fabric API."""
def __init__(self, release_track, client=None, messages=None):
self._client = client or utils.GetClientInstance(release_track)
self._messages = messages or utils.GetMessagesModule(release_track)
self._service = self._client.projects_locations_zones
def InitializeZone(self, zone_ref):
"""Initialzie a specified zone."""
zone_init_req = self._messages.EdgenetworkProjectsLocationsZonesInitializeRequest(
name=zone_ref.RelativeName())
return self._service.Initialize(zone_init_req)