def __GrpcMessages(self, files): """Parses the GRPC scoped configuraiton files into their necessary API Gateway message types. Args: files: Files to be sent in as managed service configs and GRPC service definitions Returns: List of ApigatewayApiConfigFileMessage, list of ApigatewayApiConfigGrpcServiceDefinition messages Raises: BadFileException: If there is something wrong with the files """ grpc_service_definitions = [] service_configs = [] for config_file in files: config_contents = endpoints.ReadServiceConfigFile(config_file) config_dict = self.__ValidJsonOrYaml(config_file, config_contents) if config_dict: if config_dict.get('type') == 'google.api.Service': service_configs.append( self.__MakeApigatewayApiConfigFileMessage( config_contents, config_file)) else: raise calliope_exceptions.BadFileException( 'The file {} is not a valid api configuration file. The ' 'configuration type is expected to be of "google.api.Service".' .format(config_file)) elif endpoints.IsProtoDescriptor(config_file): grpc_service_definitions.append( self.__MakeApigatewayApiConfigGrpcServiceDefinitionMessage( config_contents, config_file)) elif endpoints.IsRawProto(config_file): raise calliope_exceptions.BadFileException(( '[{}] cannot be used as it is an uncompiled proto' ' file. However, uncompiled proto files can be included for' ' display purposes when compiled as a source for a passed in proto' ' descriptor.').format(config_file)) else: raise calliope_exceptions.BadFileException(( 'Could not determine the content type of file [{0}]. Supported ' 'extensions are .descriptor .json .pb .yaml and .yml' ).format(config_file)) return service_configs, grpc_service_definitions
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' for service_config_file in 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: 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 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.' ) # Check to see if the Endpoints meta service needs to be enabled. enable_api.EnableServiceIfDisabled( properties.VALUES.core.project.Get(required=True), services_util.GetEndpointsServiceName(), args. async) # Check if we need to create the service. 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(): log.error( VALIDATE_NEW_ERROR.format( service_name=self.service_name, project_id=project_id)) return None 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) 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) # Check to see if the service is already enabled enable_api.EnableServiceIfDisabled( properties.VALUES.core.project.Get(required=True), self.service_name, args. async) return push_config_result
def __PushGrpcConfigFiles(self, files, service_name, project_id, config_id): """Creates a new ServiceConfig in SerivceManagement from gRPC files. Args: files: Files to be pushed to Service Management service_name: Name of the service to push configs to project_id: Project the service belongs to config_id: ID to assign to the new ServiceConfig Returns: ServiceConfig Id Raises: BadFileException: If there is something wrong with the files """ messages = endpoints.GetMessagesModule() file_types = messages.ConfigFile.FileTypeValueValuesEnum # TODO(b/77867100): remove .proto support and deprecation warning. give_proto_deprecate_warning = False config_files = [] for config_file in files: config_contents = endpoints.ReadServiceConfigFile(config_file) config_dict = self.__ValidJsonOrYaml(config_file, config_contents) if config_dict: if config_dict.get('type') == 'google.api.Service': config_files.append( self.__MakeConfigFileMessage( config_contents, config_file, file_types.SERVICE_CONFIG_YAML)) elif 'name' in 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(files) > 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(config_file)) return self.__PushServiceConfigFiles(files, service_name, project_id, config_id, normalized=True) else: raise calliope_exceptions.BadFileException( 'The file {} is not a valid api configuration file'. format(config_file)) elif endpoints.IsProtoDescriptor(config_file): config_files.append( self.__MakeConfigFileMessage( config_contents, config_file, file_types.FILE_DESCRIPTOR_SET_PROTO)) elif endpoints.IsRawProto(config_file): give_proto_deprecate_warning = True config_files.append( self.__MakeConfigFileMessage(config_contents, 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(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' ) return self.__PushServiceConfigFiles(config_files, service_name, project_id, config_id)