def __PushServiceConfigFiles(self, files, service_name, project_id, config_id, normalized=False): """Creates a new ServiceConfig in Service Management. 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 normalized: Whether or not this is a normalized google service Returns: ServiceConfig Id """ if normalized: config_contents = endpoints.ReadServiceConfigFile(files[0]) push_config_result = endpoints.PushNormalizedGoogleServiceConfig( service_name, project_id, endpoints.LoadJsonOrYaml(config_contents), config_id=config_id) service_config_id = push_config_result.id else: push_config_result = endpoints.PushMultipleServiceConfigFiles( service_name, files, False, config_id=config_id) service_config_id = ( endpoints.GetServiceConfigIdFromSubmitConfigSourceResponse( push_config_result)) return service_config_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' 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.\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