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,26 @@
# -*- 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.
"""Package for the service-management/services CLI subcommands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
class Services(base.Group):
"""Manage Services."""

View File

@@ -0,0 +1,70 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Add IAM policy binding to a service.
description: |
Add an IAM policy binding to a service.
Note: The 'roles/servicemanagement.serviceConsumer' role can only be added to a member which is
a user, group, or service account.
examples: |
To add an IAM policy binding with the role of 'roles/servicemanagement.serviceConsumer' for the
user 'test-user@example.com' on the service 'my-service', run:
$ {command} my-service --member='user:test-user@example.com' --role='roles/servicemanagement.serviceConsumer'
To add an IAM policy binding with the role of
'roles/servicemanagement.serviceConsumer' for the service account
'my-iam-account@my-project.iam.gserviceaccount.com' on the service 'my-service', run:
$ {command} my-service \
--member='serviceAccount:my-iam-account@my-project.iam.gserviceaccount.com' \
--role='roles/servicemanagement.serviceConsumer'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: servicemanagement.services
use_relative_name: false
arguments:
resource:
help_text: The service for which to add IAM policy binding to.
spec: !REF googlecloudsdk.command_lib.endpoints.resources:service
ALPHA:
help_text:
brief: Add IAM policy binding to a service.
description: |
Add an IAM policy binding to the IAM policy of a service. One binding consists of a member,
a role, and an optional condition.
Note: The 'roles/servicemanagement.serviceConsumer' role can only be added to a member which is
a user, group, or service account.
examples: |
To add an IAM policy binding with the role of 'roles/servicemanagement.serviceConsumer'
for the user 'test-user@example.com' on the service 'my-service', run:
$ {command} my-service --member='user:test-user@example.com' --role='roles/servicemanagement.serviceConsumer'
To add an IAM policy binding with the role of
'roles/servicemanagement.serviceConsumer' for the service account
'my-iam-account@my-project.iam.gserviceaccount.com' on the service 'my-service', run:
$ {command} my-service \
--member='serviceAccount:my-iam-account@my-project.iam.gserviceaccount.com' \
--role='roles/servicemanagement.serviceConsumer'
To add an IAM policy binding which expires at the end of the year 2018 for the role of
'roles/servicemanagement.quotaAdmin' and the user 'test-user@example.com' with service 'my-service', run:
$ {command} my-service --member='user:test-user@example.com' --role='roles/servicemanagement.quotaAdmin' --condition='expression=request.time < timestamp("2019-01-01T00:00:00Z"),title=expires_end_of_2018,description=Expires at midnight on 2018-12-31'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
See https://cloud.google.com/iam/docs/conditions-overview for details on conditions.
iam:
enable_condition: true

View File

@@ -0,0 +1,80 @@
# -*- 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.
"""Command to get information about a principal's permissions on a service."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.endpoints import services_util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.endpoints import arg_parsers
from googlecloudsdk.command_lib.endpoints import common_flags
class CheckIamPolicy(base.Command):
"""Returns information about a principal's permissions on a service.
This command lists the permissions that the current authenticated
gcloud user has for a service. For example, if the authenticated user is
able to delete the service, `servicemanagement.services.delete` will
be among the returned permissions.
## EXAMPLES
To check the permissions for the currently authenticated gcloud, run:
$ {command} my_produced_service_name
"""
@staticmethod
def Args(parser):
"""Args is called by calliope to gather arguments for this command.
Args:
parser: An argparse parser that you can use to add arguments that go
on the command line after this command. Positional arguments are
allowed.
"""
service_flag = common_flags.producer_service_flag(
suffix='for which to check the IAM policy')
service_flag.AddToParser(parser)
def Run(self, args):
"""Run 'service-management check-access'.
Args:
args: argparse.Namespace, The arguments that this command was invoked
with.
Returns:
The response from the access API call.
"""
messages = services_util.GetMessagesModule()
client = services_util.GetClientInstance()
all_iam_permissions = services_util.ALL_IAM_PERMISSIONS
# Shorten the query request name for better readability
query_request = messages.ServicemanagementServicesTestIamPermissionsRequest
service = arg_parsers.GetServiceNameFromArg(args.service)
request = query_request(
servicesId=service,
testIamPermissionsRequest=messages.TestIamPermissionsRequest(
permissions=all_iam_permissions))
return client.services.TestIamPermissions(request)

View File

@@ -0,0 +1,95 @@
# -*- 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.
"""service-management delete command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.endpoints import services_util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.endpoints import arg_parsers
from googlecloudsdk.command_lib.endpoints import common_flags
from googlecloudsdk.core.console import console_io
class Delete(base.DeleteCommand):
# pylint: disable=line-too-long
"""Deletes a service from Google Service Management.
Services that are deleted will be retained in the system for 30 days.
If a deleted service is still within this retention window, it can be
undeleted with the undelete command.
## EXAMPLES
To delete a service named `my-service`, run:
$ {command} my-service
To run the same command asynchronously (non-blocking), run:
$ {command} my-service --async
"""
# pylint: enable=line-too-long
@staticmethod
def Args(parser):
"""Args is called by calliope to gather arguments for this command.
Args:
parser: An argparse parser that you can use to add arguments that go
on the command line after this command. Positional arguments are
allowed.
"""
common_flags.producer_service_flag(suffix='to delete').AddToParser(parser)
base.ASYNC_FLAG.AddToParser(parser)
parser.display_info.AddCacheUpdater(None)
def Run(self, args):
"""Run 'service-management delete'.
Args:
args: argparse.Namespace, The arguments that this command was invoked
with.
Returns:
The response from the Delete API call (or None if cancelled).
"""
messages = services_util.GetMessagesModule()
client = services_util.GetClientInstance()
# Prompt with a warning before continuing.
console_io.PromptContinue(
message='Are you sure? This will set the service configuration to be '
'deleted, along with all of the associated consumer '
'information. Note: This does not immediately delete the '
'service configuration or data and can be undone using the '
'undelete command for 30 days. Only after 30 days will the '
'service be purged from the system.',
prompt_string='Continue anyway',
default=True,
throw_if_unattended=True,
cancel_on_no=True)
service = arg_parsers.GetServiceNameFromArg(args.service)
request = messages.ServicemanagementServicesDeleteRequest(
serviceName=service,)
operation = client.services.Delete(request)
return services_util.ProcessOperationResult(operation, args.async_)

View File

@@ -0,0 +1,694 @@
# -*- 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.
"""endpoints deploy command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
from typing import Final
from googlecloudsdk.api_lib.endpoints import config_reporter
from googlecloudsdk.api_lib.endpoints import exceptions
from googlecloudsdk.api_lib.endpoints import services_util
from googlecloudsdk.api_lib.services import enable_api
from googlecloudsdk.api_lib.services import exceptions as services_exceptions
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions as calliope_exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import http_encoding
import six.moves.urllib.parse
ADVICE_STRING = (
'Advice found for changes in the new service config. If this '
'is a --validate-only run, the config push would have failed. '
'See the outputted report for failure reason(s). If this is '
'not a --validate-only run and you would like to ignore these '
'warnings, rerun the command with --force. NOTE: setting this '
'flag will ignore all change advice. For production systems, '
'best practice is to set this for a single execution only '
'after manually reviewing all changes with advice.'
)
FORCE_ADVICE_STRING = (
'Advice found for changes in the new service config, '
'but proceeding anyway because --force is set...'
)
VALIDATE_NEW_PROMPT = (
'The service {service_name} must exist in order to '
'validate the configuration. Do you want to create the '
'service in project {project_id}?'
)
VALIDATE_NEW_ERROR = (
'The service {service_name} must exist in order to '
'validate the configuration. To create the service in '
'project {project_id}, rerun the command without the '
'--validate-only flag.'
)
NUM_ADVICE_TO_PRINT = 3
SWAGGER_VERSION: Final[str] = '2.x'
OPENAPI_V3_VERSION: Final[str] = '3.x'
def _CommonArgs(parser):
"""Add common arguments for this command to the given parser."""
parser.add_argument(
'service_config_file',
nargs='+',
help=(
'The service configuration file (or files) containing the API '
'specification to upload. Proto Descriptors, Open API (Swagger) '
'specifications, and Google Service Configuration files in JSON '
'and YAML formats are acceptable.'
),
)
base.ASYNC_FLAG.AddToParser(parser)
def GenerateManagementUrl(service):
"""Generate a service management url for this service."""
# It is actually possible to deploy a service configuration for a service
# which is not in the current project, as long as you have appropriate
# permissions. Because of this, we need to explicitly query for the service's
# project.
messages = services_util.GetMessagesModule()
client = services_util.GetClientInstance()
get_request = messages.ServicemanagementServicesGetRequest(
serviceName=service,
)
response = client.services.Get(get_request)
project = response.producerProjectId
return (
'https://console.cloud.google.com/endpoints/api/'
'{service}/overview?project={project}'.format(
service=six.moves.urllib.parse.quote(service),
project=six.moves.urllib.parse.quote(project),
)
)
class _BaseDeploy(object):
"""Create deploy base class for all release tracks."""
@staticmethod
def Args(parser):
"""Args is called by calliope to gather arguments for this command.
Args:
parser: An argparse parser that you can use to add arguments that go on
the command line after this command. Positional arguments are allowed.
"""
_CommonArgs(parser)
parser.add_argument(
'--validate-only',
action='store_true',
help=(
'If included, the command validates the service configuration(s), '
'but does not deploy them. The service must exist in order to '
'validate the configuration(s).'
),
)
parser.add_argument(
'--force',
'-f',
action='store_true',
default=False,
help=(
'Force the deployment even if any hazardous changes to the '
'service configuration are detected.'
),
)
def MakeConfigFileMessage(
self,
file_contents: str,
filename: str,
file_type,
):
"""Constructs a ConfigFile message from a config file.
Args:
file_contents: The contents of the config file.
filename: The full path to the config file.
file_type: FileTypeValueValuesEnum describing the type of config file.
Returns:
The constructed ConfigFile message.
"""
messages = services_util.GetMessagesModule()
file_types = messages.ConfigFile.FileTypeValueValuesEnum
if file_type != file_types.FILE_DESCRIPTOR_SET_PROTO:
# File is human-readable text, not binary; needs to be encoded.
file_contents = http_encoding.Encode(file_contents)
return messages.ConfigFile(
fileContents=file_contents,
filePath=os.path.basename(filename),
fileType=file_type,
)
def ShowConfigReport(self, service, service_config_id, log_func=log.warning):
"""Run and display results (if any) from the Push Advisor.
Args:
service: The name of the service for which to compare configs.
service_config_id: The new config ID to compare against the active config.
log_func: The function to which to pass advisory messages (default:
log.warning).
Returns:
The number of advisory messages returned by the Push Advisor.
"""
num_changes_with_advice = 0
reporter = config_reporter.ConfigReporter(service)
# Set the new config as the recently generated service config ID
reporter.new_config.SetConfigId(service_config_id)
# We always want to compare agaisnt the active config, so use default here
reporter.old_config.SetConfigUseDefaultId()
change_report = reporter.RunReport()
if not change_report or not change_report.configChanges:
return 0
changes = change_report.configChanges
for change in changes:
if change.advices:
if num_changes_with_advice < NUM_ADVICE_TO_PRINT:
log_func(
'%s\n', services_util.PushAdvisorConfigChangeToString(change)
)
num_changes_with_advice += 1
if num_changes_with_advice > NUM_ADVICE_TO_PRINT:
log_func(
'%s total changes with advice found, check config report file '
'for full list.\n',
num_changes_with_advice,
)
return num_changes_with_advice
def CheckPushAdvisor(self, unused_force=False):
"""Run the Push Advisor and return whether the command should abort.
Args:
unused_force: bool, unused in the default implementation.
Returns:
True if the deployment should be aborted due to warnings, otherwise
False if it's safe to continue.
"""
# Child classes must override this; otherwise, we'll always return False
return False
def AttemptToEnableService(self, service_name, is_async):
"""Attempt to enable a service. If lacking permission, log a warning.
Args:
service_name: The service to enable.
is_async: If true, return immediately instead of waiting for the operation
to finish.
"""
project_id = properties.VALUES.core.project.Get(required=True)
try:
# Enable the produced service.
enable_api.EnableService(project_id, service_name, is_async)
# The above command will print a message to the human user, but it needs a
# newline when the command is successful.
log.status.Print('\n')
except services_exceptions.EnableServiceException:
log.warning(
(
'Attempted to enable service [{0}] on project [{1}], but '
'did not have required permissions. Please ensure this '
'service is enabled before using your Endpoints '
'service.\n'
).format(service_name, project_id)
)
def Run(self, args):
"""Run 'endpoints services deploy'.
Args:
args: argparse.Namespace, The arguments that this command was invoked
with.
Returns:
The response from the Update API call.
Raises:
BadFileExceptionn: if the provided service configuration files are
invalid or cannot be read.
"""
messages = services_util.GetMessagesModule()
client = services_util.GetClientInstance()
file_types = messages.ConfigFile.FileTypeValueValuesEnum
self.service_name = self.service_version = config_contents = None
config_files = []
self.validate_only = args.validate_only
# TODO(b/77867100): remove .proto support and deprecation warning.
give_proto_deprecate_warning = False
# If we're not doing a validate-only run, we don't want to output the
# resource directly unless the user specifically requests it using the
# --format flag. The Epilog will show useful information after deployment
# is complete.
if not self.validate_only and not args.IsSpecified('format'):
args.format = 'none'
oas_version = None
for i, service_config_file in enumerate(args.service_config_file):
config_contents = services_util.ReadServiceConfigFile(service_config_file)
if services_util.FilenameMatchesExtension(
service_config_file, ['.json', '.yaml', '.yml']
):
# Try to load the file as JSON. If that fails, try YAML.
service_config_dict = services_util.LoadJsonOrYaml(config_contents)
if not service_config_dict:
raise calliope_exceptions.BadFileException(
'Could not read JSON or YAML from service config file '
'[{0}].'.format(service_config_file)
)
if 'swagger' in service_config_dict:
# Only one OpenAPI version is supported per deployment.
if oas_version and oas_version != SWAGGER_VERSION:
raise calliope_exceptions.BadFileException(
(
'Malformed input. Found OpenAPI service config in file'
' [{}], but a different OpenAPI version was'
' specified in another file. Only one OpenAPI version is'
' supported per deployment.'
).format(service_config_file)
)
if not oas_version:
oas_version = SWAGGER_VERSION
if 'host' not in service_config_dict:
raise calliope_exceptions.BadFileException(
(
'Malformed input. Found Swagger service config in file'
' [{}], but no host was specified. Add a host specification'
' to the config file.'
).format(service_config_file)
)
if not self.service_name and service_config_dict.get('host'):
self.service_name = service_config_dict.get('host')
# Always use YAML for Open API because JSON is a subset of YAML.
config_files.append(
self.MakeConfigFileMessage(
config_contents, service_config_file, file_types.OPEN_API_YAML
)
)
elif 'openapi' in service_config_dict:
# Only one OpenAPI version is supported per deployment.
if oas_version and oas_version != OPENAPI_V3_VERSION:
raise calliope_exceptions.BadFileException(
(
'Malformed input. Found OpenAPI service config in file'
' [{}], but a different OpenAPI version was'
' specified in another file. Only one OpenAPI version is'
' supported per deployment.'
).format(service_config_file)
)
if not oas_version:
oas_version = OPENAPI_V3_VERSION
# For Endpoints, exactly only one server may be configured with the
# "x-google-endpoint" extension per file and one must be configured
# across all provided files. The server's hostname will be used as the
# service name. If multiple oas files are provided, the service name
# must be the same across all files.
found_x_google_endpoint = False
x_google_endpoint_service_name = None
for s in service_config_dict.get('servers') or []:
if 'x-google-endpoint' in s:
if found_x_google_endpoint:
raise calliope_exceptions.BadFileException(
(
'Malformed input. Found OpenAPI service config in file'
' [{}], but multiple servers were specified with the'
' "x-google-endpoint" extension. At most one server'
' can have the "x-google-endpoint" extension per file.'
).format(service_config_file)
)
if 'url' not in s:
raise calliope_exceptions.BadFileException(
(
'Malformed input. Found OpenAPI service config in file'
' [{}], but no url was specified for the server with'
' the "x-google-endpoint" extension. Add a url'
' specification to the server with the'
' "x-google-endpoint" extension.'
).format(service_config_file)
)
x_google_endpoint_service_name = six.moves.urllib.parse.urlparse(
s.get('url')
).hostname
found_x_google_endpoint = True
# If this is the first file with the "x-google-endpoint" extension,
# use the server URL's hostname as the service name.
if self.service_name is None and x_google_endpoint_service_name:
self.service_name = x_google_endpoint_service_name
# If this is the last (or only) file and no server with the
# "x-google-endpoint" extension was found, raise an error.
if (
i == len(args.service_config_file) - 1
and self.service_name is None
):
raise calliope_exceptions.BadFileException(
'Malformed input. No servers were specified with the'
' "x-google-endpoint" extension in the provided OpenAPI'
' file(s). Add a servers specification to at least one config'
' file with exactly one server configured with the'
' "x-google-endpoint" extension.'
)
# If multiple files are provided, ensure that all files with the
# "x-google-endpoint" extension define the same server URL. This
# ensures that the same service name is used for all files.
if (
self.service_name
and x_google_endpoint_service_name
and self.service_name != x_google_endpoint_service_name
):
raise calliope_exceptions.BadFileException((
'Ambiguous input. Multiple OpenAPI files were provided with'
' different server URLs containing the "x-google-endpoint"'
' extension. When using multiple OpenAPI files, ensure that all'
' files with the "x-google-endpoint" extension define the same'
' server URL with at most one server with the'
' "x-google-endpoint" extension defined per file. Additionally,'
' at least one server across all provided files must have the'
' "x-google-endpoint" extension.'
))
# Always use YAML for Open API because JSON is a subset of YAML.
config_files.append(
self.MakeConfigFileMessage(
config_contents, service_config_file, file_types.OPEN_API_YAML
)
)
elif service_config_dict.get('type') == 'google.api.Service':
if not self.service_name and service_config_dict.get('name'):
self.service_name = service_config_dict.get('name')
config_files.append(
self.MakeConfigFileMessage(
config_contents,
service_config_file,
file_types.SERVICE_CONFIG_YAML,
)
)
elif 'name' in service_config_dict:
# This is a special case. If we have been provided a Google Service
# Configuration file which has a service 'name' field, but no 'type'
# field, we have to assume that this is a normalized service config,
# and can be uploaded via the CreateServiceConfig API. Therefore,
# we can short circute the process here.
if len(args.service_config_file) > 1:
raise calliope_exceptions.BadFileException(
(
'Ambiguous input. Found normalized service configuration in'
' file [{0}], but received multiple input files. To upload'
' normalized service config, please provide it separately'
' from other input files to avoid ambiguity.'
).format(service_config_file)
)
# If this is a validate-only run, abort now, since this is not
# supported in the ServiceConfigs.Create API
if self.validate_only:
raise exceptions.InvalidFlagError(
'The --validate-only flag is not supported when using '
'normalized service configs as input.'
)
self.service_name = service_config_dict.get('name')
config_files = []
break
else:
raise calliope_exceptions.BadFileException(
(
'Unable to parse Open API, or Google Service Configuration '
'specification from {0}'
).format(service_config_file)
)
elif services_util.IsProtoDescriptor(service_config_file):
config_files.append(
self.MakeConfigFileMessage(
config_contents,
service_config_file,
file_types.FILE_DESCRIPTOR_SET_PROTO,
)
)
elif services_util.IsRawProto(service_config_file):
give_proto_deprecate_warning = True
config_files.append(
self.MakeConfigFileMessage(
config_contents, service_config_file, file_types.PROTO_FILE
)
)
else:
raise calliope_exceptions.BadFileException(
(
'Could not determine the content type of file [{0}]. Supported '
'extensions are .json .yaml .yml .pb and .descriptor'
).format(service_config_file)
)
if give_proto_deprecate_warning:
log.warning(
'Support for uploading uncompiled .proto files is deprecated and '
'will soon be removed. Use compiled descriptor sets (.pb) instead.\n'
)
# Check if we need to create the service.
was_service_created = False
if not services_util.DoesServiceExist(self.service_name):
project_id = properties.VALUES.core.project.Get(required=True)
# Deploying, even with validate-only, cannot succeed without the service
# being created
if self.validate_only:
if not console_io.CanPrompt():
raise exceptions.InvalidConditionError(
VALIDATE_NEW_ERROR.format(
service_name=self.service_name, project_id=project_id
)
)
if not console_io.PromptContinue(
VALIDATE_NEW_PROMPT.format(
service_name=self.service_name, project_id=project_id
)
):
return None
services_util.CreateService(self.service_name, project_id)
was_service_created = True
if config_files:
push_config_result = services_util.PushMultipleServiceConfigFiles(
self.service_name,
config_files,
args.async_,
validate_only=self.validate_only,
)
self.service_config_id = (
services_util.GetServiceConfigIdFromSubmitConfigSourceResponse(
push_config_result
)
)
else:
push_config_result = services_util.PushNormalizedGoogleServiceConfig(
self.service_name,
properties.VALUES.core.project.Get(required=True),
services_util.LoadJsonOrYaml(config_contents),
)
self.service_config_id = push_config_result.id
if not self.service_config_id:
raise exceptions.InvalidConditionError(
'Failed to retrieve Service Configuration Id.'
)
# Run the Push Advisor to see if we need to warn the user of any
# potentially hazardous changes to the service configuration.
if self.CheckPushAdvisor(args.force):
return None
# Create a Rollout for the new service configuration
if not self.validate_only:
percentages = messages.TrafficPercentStrategy.PercentagesValue()
percentages.additionalProperties.append((
messages.TrafficPercentStrategy.PercentagesValue.AdditionalProperty(
key=self.service_config_id, value=100.0
)
))
traffic_percent_strategy = messages.TrafficPercentStrategy(
percentages=percentages
)
rollout = messages.Rollout(
serviceName=self.service_name,
trafficPercentStrategy=traffic_percent_strategy,
)
rollout_create = messages.ServicemanagementServicesRolloutsCreateRequest(
rollout=rollout,
serviceName=self.service_name,
)
rollout_operation = client.services_rollouts.Create(rollout_create)
services_util.ProcessOperationResult(rollout_operation, args.async_)
if was_service_created:
self.AttemptToEnableService(self.service_name, args.async_)
return push_config_result
def Epilog(self, resources_were_displayed):
# Print this to screen not to the log because the output is needed by the
# human user. Only print this when not doing a validate-only run.
if resources_were_displayed and not self.validate_only:
log.status.Print(
('Service Configuration [{0}] uploaded for service [{1}]\n').format(
self.service_config_id, self.service_name
)
)
management_url = GenerateManagementUrl(self.service_name)
log.status.Print('To manage your API, go to: ' + management_url)
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class Deploy(_BaseDeploy, base.Command):
# pylint: disable=line-too-long
"""Deploys a service configuration for the given service name.
This command is used to deploy a service configuration for a service
to Google Service Management. As input, it takes one or more paths
to service configurations that should be uploaded. These configuration
files can be Proto Descriptors, Open API (Swagger) specifications,
or Google Service Configuration files in JSON or YAML formats.
If a service name is present in multiple configuration files (given
in the `host` field in OpenAPI specifications or the `name` field in
Google Service Configuration files), the first one will take precedence.
This command will block until deployment is complete unless the
`--async` flag is passed.
## EXAMPLES
To deploy a single Open API service configuration, run:
$ {command} ~/my_app/openapi.json
To run the deployment asynchronously (non-blocking), run:
$ {command} ~/my_app/openapi.json --async
To deploy a service config with a Proto, run:
$ {command} ~/my_app/service-config.yaml ~/my_app/service-protos.pb
"""
# pylint: enable=line-too-long
# TODO(b/65455903) Once PushAdvisor is ready to be included by default,
# merge this back into _BaseDeploy and rename it to Deploy.
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
@base.DefaultUniverseOnly
class DeployBetaAlpha(_BaseDeploy, base.Command):
# pylint: disable=line-too-long
"""Deploys a service configuration for the given service name.
This command is used to deploy a service configuration for a service
to Google Service Management. As input, it takes one or more paths
to service configurations that should be uploaded. These configuration
files can be Proto Descriptors, Open API (Swagger) specifications,
or Google Service Configuration files in JSON or YAML formats.
If a service name is present in multiple configuration files (given
in the `host` field in OpenAPI specifications or the `name` field in
Google Service Configuration files), the first one will take precedence.
When deploying a new service configuration to an already-existing
service, some safety checks will be made comparing the new configuration
to the active configuration. If any actionable advice is provided, it
will be printed out to the log, and the deployment will be halted. It is
recommended that these warnings be addressed before proceeding, but they
can be overridden with the --force flag.
This command will block until deployment is complete unless the
`--async` flag is passed.
## EXAMPLES
To deploy a single Open API service configuration, run:
$ {command} ~/my_app/openapi.json
To run the deployment asynchronously (non-blocking), run:
$ {command} ~/my_app/openapi.json --async
To deploy a service config with a Proto, run:
$ {command} ~/my_app/service-config.yaml ~/my_app/service-protos.pb
"""
# pylint: enable=line-too-long
def CheckPushAdvisor(self, force=False):
"""Run the Push Advisor and return whether the command should abort.
Args:
force: bool, if True, this method will return False even if warnings are
generated.
Returns:
True if the deployment should be aborted due to warnings, otherwise
False if it's safe to continue.
"""
log_func = log.warning if force else log.error
num_advices = self.ShowConfigReport(
self.service_name, self.service_config_id, log_func
)
if num_advices > 0:
if force:
log_func(('{0}\n').format(FORCE_ADVICE_STRING))
else:
log_func(('{0}\n').format(ADVICE_STRING))
return True
return False

View File

@@ -0,0 +1,60 @@
# -*- 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.
"""service-management describe command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.endpoints import services_util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.endpoints import arg_parsers
from googlecloudsdk.command_lib.endpoints import common_flags
class Describe(base.DescribeCommand):
"""Describes a service given a service name."""
@staticmethod
def Args(parser):
"""Args is called by calliope to gather arguments for this command.
Args:
parser: An argparse parser that you can use to add arguments that go
on the command line after this command. Positional arguments are
allowed.
"""
common_flags.producer_service_flag(suffix='to describe').AddToParser(parser)
def Run(self, args):
"""Run 'service-management describe'.
Args:
args: argparse.Namespace, The arguments that this command was invoked
with.
Returns:
The response from the Get API call.
"""
messages = services_util.GetMessagesModule()
client = services_util.GetClientInstance()
service = arg_parsers.GetServiceNameFromArg(args.service)
request = messages.ServicemanagementServicesGetRequest(
serviceName=service,)
return client.services.Get(request)

View File

@@ -0,0 +1,77 @@
# -*- 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.
"""Command to describe the access policy for a service."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.endpoints import services_util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.endpoints import arg_parsers
from googlecloudsdk.command_lib.endpoints import common_flags
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class GetIamPolicy(base.ListCommand):
"""Describes the IAM policy for a service.
Gets the IAM policy for a produced service, given the service name.
## EXAMPLES
To print the IAM policy for a service named `my-service`, run:
$ {command} my-service
"""
@staticmethod
def Args(parser):
"""Args is called by calliope to gather arguments for this command.
Args:
parser: An argparse parser that you can use to add arguments that go
on the command line after this command. Positional arguments are
allowed.
"""
service_flag = common_flags.producer_service_flag(
suffix='whose IAM policy is to be described')
service_flag.AddToParser(parser)
base.URI_FLAG.RemoveFromParser(parser)
def Run(self, args):
"""Run 'service-management get-iam-policy'.
Args:
args: argparse.Namespace, The arguments that this command was invoked
with.
Returns:
The response from the access API call.
Raises:
HttpException: An http error response was received while executing api
request.
"""
messages = services_util.GetMessagesModule()
client = services_util.GetClientInstance()
service = arg_parsers.GetServiceNameFromArg(args.service)
request = messages.ServicemanagementServicesGetIamPolicyRequest(
servicesId=service)
return client.services.GetIamPolicy(request)

View File

@@ -0,0 +1,24 @@
- release_tracks: [ALPHA]
help_text:
brief: Get the IAM policy for a service.
description: |
*{command}* displays the IAM policy associated with a produced
service. If formatted as JSON, the output can be edited and used as
a policy file for *set-iam-policy*. The output includes an "etag"
field identifying the version emitted and allowing detection of
concurrent policy updates;
see $ {parent_command} set-iam-policy for additional details.
examples: |
To print the IAM policy for a given cluster, run:
$ {command} my-service
request:
collection: servicemanagement.services
use_relative_name: false
arguments:
resource:
help_text: The service for which to display the IAM policy.
spec: !REF googlecloudsdk.command_lib.endpoints.resources:service

View File

@@ -0,0 +1,83 @@
# -*- 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.
"""service-management list command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import list_pager
from googlecloudsdk.api_lib.endpoints import services_util
from googlecloudsdk.calliope import base
class List(base.ListCommand):
"""List services for a project.
This command lists the services that are produced by a project.
## EXAMPLES
To list the services the current project produces, run:
$ {command}
"""
_DEFAULT_PAGE_SIZE = 2000
@staticmethod
def Args(parser):
"""Args is called by calliope to gather arguments for this command.
Args:
parser: An argparse parser that you can use to add arguments that go
on the command line after this command. Positional arguments are
allowed.
"""
# Remove unneeded list-related flags from parser
base.URI_FLAG.RemoveFromParser(parser)
parser.display_info.AddFormat("""
table(
serviceName:label=NAME,
serviceConfig.title
)
""")
def Run(self, args):
"""Run 'endpoints list'.
Args:
args: argparse.Namespace, The arguments that this command was invoked
with.
Returns:
The list of managed services for this project.
"""
client = services_util.GetClientInstance()
validated_project = services_util.GetValidatedProject(args.project)
request = services_util.GetProducedListRequest(validated_project)
return list_pager.YieldFromList(
client.services,
request,
limit=args.limit,
batch_size_attribute='pageSize',
batch_size=args.page_size or self._DEFAULT_PAGE_SIZE,
field='services')

View File

@@ -0,0 +1,53 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Remove IAM policy binding from a service.
description: |
Remove an IAM policy binding from a service.
Note: The 'roles/servicemanagement.serviceConsumer' role can only exist on a member which is a
user, group, or service account.
examples: |
To remove an IAM policy binding for the role of 'roles/servicemanagement.serviceConsumer'
for the user 'test-user@gmail.com' with service 'my-service', run:
$ {command} my-service --member='user:test-user@gmail.com' --role='roles/servicemanagement.serviceConsumer'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: servicemanagement.services
use_relative_name: false
arguments:
resource:
help_text: The device registry for which to remove IAM policy binding from.
spec: !REF googlecloudsdk.command_lib.endpoints.resources:service
ALPHA:
help_text:
brief: Remove IAM policy binding of a service.
description: |
Remove an IAM policy binding from the IAM policy of a service. One binding consists of a member,
a role, and an optional condition.
Note: The 'roles/servicemanagement.serviceConsumer' role can only exist on a member which is a
user, group, or service account.
examples: |
To remove an IAM policy binding for the role of 'roles/servicemanagement.serviceConsumer'
for the user 'test-user@gmail.com' with service 'my-service', run:
$ {command} my-service --member='user:test-user@gmail.com' --role='roles/servicemanagement.serviceConsumer'
To remove an IAM policy binding which expires at the end of the year 2018 for the role of
'roles/servicemanagement.quotaAdmin' and the user 'test-user@gmail.com' with service 'my-service', run:
$ {command} my-service --member='user:test-user@gmail.com' --role='roles/servicemanagement.quotaAdmin' --condition='expression=request.time < timestamp("2019-01-01T00:00:00Z"),title=expires_end_of_2018,description=Expires at midnight on 2018-12-31'
See https://cloud.google.com/iam/docs/managing-policies for details on
policy role and member types.
See https://cloud.google.com/iam/docs/conditions-overview for details on conditions.
iam:
enable_condition: true

View File

@@ -0,0 +1,82 @@
# -*- 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.
"""service-management undelete command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.endpoints import services_util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.endpoints import arg_parsers
from googlecloudsdk.command_lib.endpoints import common_flags
class Undelete(base.Command):
# pylint: disable=line-too-long
"""Undeletes a service configuration that was previously deleted.
Services that are deleted will be retained in the system for 30 days.
If a deleted service is still within this retention window, it can be
undeleted with this command.
Note that this means that this command will not be effective for
service configurations marked for deletion more than 30 days ago.
## EXAMPLES
To undelete a service named `my-service`, run:
$ {command} my-service
To run the same command asynchronously (non-blocking), run:
$ {command} my-service --async
"""
# pylint: enable=line-too-long
@staticmethod
def Args(parser):
"""Args is called by calliope to gather arguments for this command.
Args:
parser: An argparse parser that you can use to add arguments that go
on the command line after this command. Positional arguments are
allowed.
"""
common_flags.producer_service_flag(suffix='to undelete').AddToParser(parser)
base.ASYNC_FLAG.AddToParser(parser)
def Run(self, args):
"""Run 'service-management undelete'.
Args:
args: argparse.Namespace, The arguments that this command was invoked
with.
Returns:
The response from the Undelete API call (or None if cancelled).
"""
messages = services_util.GetMessagesModule()
client = services_util.GetClientInstance()
service = arg_parsers.GetServiceNameFromArg(args.service)
request = messages.ServicemanagementServicesUndeleteRequest(
serviceName=service,)
operation = client.services.Undelete(request)
return services_util.ProcessOperationResult(operation, args.async_)