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,44 @@
# -*- 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.
"""The gcloud app services group."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA)
class Services(base.Group):
"""View and manage your App Engine services.
This set of commands can be used to view and manage your existing App Engine
services.
To create new deployments, use `{parent_command} deploy`.
For more information on App Engine services, see:
https://cloud.google.com/appengine/docs/python/an-overview-of-app-engine
"""
category = base.APP_ENGINE_CATEGORY
detailed_help = {
'EXAMPLES': """\
To list your deployed services, run:
$ {command} list
""",
}

View File

@@ -0,0 +1,92 @@
# -*- 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.
"""The Browse command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import browser_dispatcher
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
class Browse(base.Command):
"""Open the specified service(s) in a browser.
"""
detailed_help = {
'EXAMPLES': """\
To show the url for the default service in the browser, run:
$ {command} default
To show version `v1` of service `myService` in the browser, run:
$ {command} myService --version="v1"
To show multiple services side-by-side, run:
$ {command} default otherService
To show multiple services side-by-side with a specific version, run:
$ {command} s1 s2 --version=v1
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat("""
table(
service:label=SERVICE,
url:label=URL
)
""")
flags.LAUNCH_BROWSER.AddToParser(parser)
parser.add_argument(
'services',
nargs='+',
help="""\
The services to open (optionally filtered by the --version flag).""")
parser.add_argument(
'--version',
'-v',
help="""\
If specified, open services with a given version. If not
specified, use a version based on the service's traffic split .
""")
def Run(self, args):
"""Launch a browser, or return a table of URLs to print."""
project = properties.VALUES.core.project.Get(required=True)
outputs = []
for service in args.services:
outputs.append(browser_dispatcher.BrowseApp(project,
service,
args.version,
args.launch_browser))
if any(outputs):
if args.launch_browser:
# We couldn't find a browser to launch
log.status.Print(
'Did not detect your browser. Go to these links to view your app:')
return outputs
return None

View File

@@ -0,0 +1,76 @@
# -*- 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.
"""`gcloud app services delete` command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.api_lib.app import service_util
from googlecloudsdk.api_lib.app import version_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import text
from six import moves
class Delete(base.DeleteCommand):
"""Delete services in the current project."""
detailed_help = {
'EXAMPLES': """\
To delete a service (and all of its accompanying versions) in the
current project, run:
$ {command} service1
To delete multiple services (and all of their accompanying versions)
in the current project, run:
$ {command} service1 service2
""",
}
@staticmethod
def Args(parser):
parser.add_argument('services', nargs='+', help='The service(s) to delete.')
parser.add_argument(
'--version', help='Delete a specific version of the given service(s).')
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
# Why do this? It lets us know if we're missing services up front (fail
# fast), and we get to control the error messages
all_services = api_client.ListServices()
services = service_util.GetMatchingServices(all_services, args.services)
if args.version:
console_io.PromptContinue(
'Deleting version [{0}] of {1} [{2}].'.format(
args.version, text.Pluralize(len(services), 'service'),
', '.join(moves.map(str, services))),
cancel_on_no=True)
versions = [version_util.Version(api_client.project, s.id, args.version)
for s in services]
version_util.DeleteVersions(api_client, versions)
else:
console_io.PromptContinue(
'Deleting {0} [{1}].'.format(
text.Pluralize(len(services), 'service'),
', '.join(moves.map(str, services))),
cancel_on_no=True)
service_util.DeleteServices(api_client, services)

View File

@@ -0,0 +1,46 @@
# -*- 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.
"""The `app services describe` command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA)
class Describe(base.Command):
"""Display all data about an existing service."""
detailed_help = {
'EXAMPLES': """\
To show all the data about service s1, run
$ {command} s1
""",
}
@staticmethod
def Args(parser):
parser.add_argument(
'service',
help='The service to describe.')
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
return api_client.GetServiceResource(args.service)

View File

@@ -0,0 +1,61 @@
# -*- 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.
"""`gcloud app services list` command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.calliope import base
class List(base.ListCommand):
"""List your existing services.
This command lists all services that are currently deployed to the App Engine
server.
"""
detailed_help = {
'EXAMPLES': """\
To list all services in the current project, run:
$ {command}
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat("""
table(
id:label=SERVICE:sort=1,
versions.len():label=NUM_VERSIONS
)
""")
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
services = api_client.ListServices()
versions = api_client.ListVersions(services)
result = []
for service in services:
versions_for_service = [v for v in versions if v.service == service.id]
result.append(
{'id': service.id, 'versions': versions_for_service})
# Sort so the order is deterministic for testing.
return sorted(result, key=lambda r: r['id'])

View File

@@ -0,0 +1,135 @@
# -*- 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.
"""`gcloud app services set-traffic` command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.api_lib.app import operations_util
from googlecloudsdk.api_lib.app import service_util
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.resource import resource_printer
import six
class TrafficSplitError(exceptions.Error):
"""Errors occurring when setting traffic splits."""
pass
class SetTraffic(base.Command):
"""Set traffic splitting settings.
This command sets the traffic split of versions across a service or a project.
"""
detailed_help = {
'EXAMPLES': """\
To send all traffic to 'v2' of service 's1', run:
$ {command} s1 --splits=v2=1
To split traffic evenly between 'v1' and 'v2' of service 's1', run:
$ {command} s1 --splits=v2=.5,v1=.5
To split traffic across all services:
$ {command} --splits=v2=.5,v1=.5
""",
}
@staticmethod
def Args(parser):
parser.add_argument('services', nargs='*', help=(
'The services to modify.'))
parser.add_argument(
'--splits',
required=True,
type=arg_parsers.ArgDict(min_length=1),
help="""\
Key-value pairs describing what proportion of traffic should go to
each version. The split values are added together and used as
weights. The exact values do not matter, only their relation to each
other. For example, v1=2,v2=2 is equivalent to v1=.5,v2=.5""")
parser.add_argument(
'--split-by',
choices=['cookie', 'ip', 'random'],
default='ip',
help='Whether to split traffic based on cookie, IP address or random.')
parser.add_argument(
'--migrate',
action='store_true',
default=False,
help="""\
The migrate flag determines whether or not to use traffic migration
during the operation. Traffic migration will attempt to automatically
migrate traffic from the previous version to the new version, giving
the autoscaler time to respond. See the documentation here:
[](https://cloud.google.com/appengine/docs/python/console/trafficmigration)
for more information.""")
def Run(self, args):
if args.migrate and len(args.splits) > 1:
raise TrafficSplitError('The migrate flag can only be used with splits '
'to a single version.')
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
all_services = api_client.ListServices()
services = service_util.GetMatchingServices(all_services, args.services)
allocations = service_util.ParseTrafficAllocations(
args.splits, args.split_by)
display_allocations = []
for service in services:
for version, split in six.iteritems(allocations):
display_allocations.append('{0}/{1}/{2}: {3}'.format(
api_client.project,
service.id,
version,
split))
fmt = 'list[title="Setting the following traffic allocation:"]'
resource_printer.Print(display_allocations, fmt, out=log.status)
log.status.Print(
'NOTE: Splitting traffic by {0}.'.format(args.split_by))
log.status.Print('Any other versions of the specified service will '
'receive zero traffic.')
console_io.PromptContinue(cancel_on_no=True)
errors = {}
for service in services:
try:
operations_util.CallAndCollectOpErrors(
api_client.SetTrafficSplit, service.id, allocations,
args.split_by.upper(), args.migrate)
except operations_util.MiscOperationError as err:
errors[service.id] = six.text_type(err)
if errors:
printable_errors = {}
for service, error_msg in errors.items():
printable_errors[service] = error_msg
raise TrafficSplitError(
'Issue setting traffic on service(s): {0}\n\n'.format(
', '.join(list(printable_errors.keys()))) +
'\n\n'.join(list(printable_errors.values())))

View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 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.
"""`gcloud app services update` command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.api_lib.app import operations_util
from googlecloudsdk.api_lib.app import service_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core import exceptions
import six
class IngressSettingError(exceptions.Error):
"""Errors occurring when setting ingress settings."""
pass
@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA)
class Update(base.Command):
"""Update service-level settings.
Update ingress traffic settings for an app.
"""
detailed_help = {
'EXAMPLES': """To update ingress traffic settings for """
"""the default service, run:
$ {command} default --ingress=internal-only
""",
}
@staticmethod
def Args(parser):
parser.add_argument('services', nargs='*', help=('The services to modify.'))
parser.add_argument(
'--ingress',
choices=['all', 'internal-only', 'internal-and-cloud-load-balancing'],
default='all',
required=True,
help='Control what traffic can reach the app.')
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
all_services = api_client.ListServices()
services = service_util.GetMatchingServices(all_services, args.services)
errors = {}
for service in services:
ingress_traffic_allowed = (
api_client.messages.NetworkSettings
.IngressTrafficAllowedValueValuesEnum.INGRESS_TRAFFIC_ALLOWED_ALL)
if args.ingress == 'internal-only':
ingress_traffic_allowed = (
api_client.messages.NetworkSettings
.IngressTrafficAllowedValueValuesEnum
.INGRESS_TRAFFIC_ALLOWED_INTERNAL_ONLY)
elif args.ingress == 'internal-and-cloud-load-balancing':
ingress_traffic_allowed = (
api_client.messages.NetworkSettings
.IngressTrafficAllowedValueValuesEnum
.INGRESS_TRAFFIC_ALLOWED_INTERNAL_AND_LB)
try:
operations_util.CallAndCollectOpErrors(
api_client.SetIngressTrafficAllowed, service.id,
ingress_traffic_allowed)
except operations_util.MiscOperationError as err:
errors[service.id] = six.text_type(err)
if errors:
combined_error_msg = 'Error updating service(s): '
for service, error_msg in errors.items():
combined_error_msg += '\n- %s\n %s' % (service, error_msg)
raise IngressSettingError(combined_error_msg + '\n\n')