def GetConfiguration(self, service_or_configuration_ref): """Return the relevant Configuration from the server, or None if 404.""" messages = self.messages_module if hasattr(service_or_configuration_ref, 'servicesId'): name = self._registry.Parse( service_or_configuration_ref.servicesId, params={ 'namespacesId': service_or_configuration_ref.namespacesId, }, collection='run.namespaces.configurations').RelativeName() else: name = service_or_configuration_ref.RelativeName() configuration_get_request = ( messages.RunNamespacesConfigurationsGetRequest( name=name)) try: with metrics.RecordDuration(metric_names.GET_CONFIGURATION): configuration_get_response = self._client.namespaces_configurations.Get( configuration_get_request) return configuration.Configuration(configuration_get_response, messages) except api_exceptions.InvalidDataFromServerError as e: serverless_exceptions.MaybeRaiseCustomFieldMismatch(e) except api_exceptions.HttpNotFoundError: return None
def GetRevisionsByNonce(self, namespace_ref, nonce): """Return all revisions with the given nonce.""" messages = self.messages_module request = messages.RunNamespacesRevisionsListRequest( parent=namespace_ref.RelativeName(), labelSelector='{} = {}'.format(revision.NONCE_LABEL, nonce)) try: response = self._client.namespaces_revisions.List(request) return [revision.Revision(item, messages) for item in response.items] except api_exceptions.InvalidDataFromServerError as e: serverless_exceptions.MaybeRaiseCustomFieldMismatch(e)
def ListServices(self, namespace_ref): """Returns all services in the namespace.""" messages = self.messages_module request = messages.RunNamespacesServicesListRequest( parent=namespace_ref.RelativeName()) try: with metrics.RecordDuration(metric_names.LIST_SERVICES): response = self._client.namespaces_services.List(request) return [service.Service(item, messages) for item in response.items] except api_exceptions.InvalidDataFromServerError as e: serverless_exceptions.MaybeRaiseCustomFieldMismatch(e)
def GetService(self, service_ref): """Return the relevant Service from the server, or None if 404.""" messages = self.messages_module service_get_request = messages.RunNamespacesServicesGetRequest( name=service_ref.RelativeName()) try: with metrics.RecordDuration(metric_names.GET_SERVICE): service_get_response = self._client.namespaces_services.Get( service_get_request) return service.Service(service_get_response, messages) except api_exceptions.InvalidDataFromServerError as e: serverless_exceptions.MaybeRaiseCustomFieldMismatch(e) except api_exceptions.HttpNotFoundError: return None
def GetRevision(self, revision_ref): """Get the revision. Args: revision_ref: Resource, revision to get. Returns: A revision.Revision object. """ messages = self.messages_module revision_name = revision_ref.RelativeName() request = messages.RunNamespacesRevisionsGetRequest( name=revision_name) try: with metrics.RecordDuration(metric_names.GET_REVISION): response = self._client.namespaces_revisions.Get(request) return revision.Revision(response, messages) except api_exceptions.InvalidDataFromServerError as e: serverless_exceptions.MaybeRaiseCustomFieldMismatch(e) except api_exceptions.HttpNotFoundError: return None
def ListRevisions(self, namespace_ref, service_name, limit=None, page_size=100): """List all revisions for the given service. Revision list gets sorted by service name and creation timestamp. Args: namespace_ref: Resource, namespace to list revisions in service_name: str, The service for which to list revisions. limit: Optional[int], max number of revisions to list. page_size: Optional[int], number of revisions to fetch at a time Yields: Revisions for the given surface """ messages = self.messages_module # NB: This is a hack to compensate for apitools not generating this line. # It's necessary to make the URL parameter be "continue". encoding.AddCustomJsonFieldMapping( messages.RunNamespacesRevisionsListRequest, 'continue_', 'continue') request = messages.RunNamespacesRevisionsListRequest( parent=namespace_ref.RelativeName(), ) if service_name is not None: # For now, same as the service name, and keeping compatible with # 'service-less' operation. request.labelSelector = 'serving.knative.dev/service = {}'.format( service_name) try: for result in list_pager.YieldFromList( service=self._client.namespaces_revisions, request=request, limit=limit, batch_size=page_size, current_token_attribute='continue_', next_token_attribute=('metadata', 'continue_'), batch_size_attribute='limit'): yield revision.Revision(result, messages) except api_exceptions.InvalidDataFromServerError as e: serverless_exceptions.MaybeRaiseCustomFieldMismatch(e)
def _UpdateOrCreateService( self, service_ref, config_changes, with_code, serv): """Apply config_changes to the service. Create it if necessary. Arguments: service_ref: Reference to the service to create or update config_changes: list of ConfigChanger to modify the service with with_code: bool, True if the config_changes contains code to deploy. We can't create the service if we're not deploying code. serv: service.Service, For update the Service to update and for create None. Returns: The Service object we created or modified. """ messages = self.messages_module try: if serv: # PUT the changed Service for config_change in config_changes: serv = config_change.Adjust(serv) serv_name = service_ref.RelativeName() serv_update_req = ( messages.RunNamespacesServicesReplaceServiceRequest( service=serv.Message(), name=serv_name)) with metrics.RecordDuration(metric_names.UPDATE_SERVICE): updated = self._client.namespaces_services.ReplaceService( serv_update_req) return service.Service(updated, messages) else: if not with_code: raise serverless_exceptions.ServiceNotFoundError( 'Service [{}] could not be found.'.format(service_ref.servicesId)) # POST a new Service new_serv = service.Service.New(self._client, service_ref.namespacesId) new_serv.name = service_ref.servicesId parent = service_ref.Parent().RelativeName() for config_change in config_changes: new_serv = config_change.Adjust(new_serv) serv_create_req = ( messages.RunNamespacesServicesCreateRequest( service=new_serv.Message(), parent=parent)) with metrics.RecordDuration(metric_names.CREATE_SERVICE): raw_service = self._client.namespaces_services.Create( serv_create_req) return service.Service(raw_service, messages) except api_exceptions.InvalidDataFromServerError as e: serverless_exceptions.MaybeRaiseCustomFieldMismatch(e) except api_exceptions.HttpBadRequestError as e: exceptions.reraise(serverless_exceptions.HttpError(e)) except api_exceptions.HttpNotFoundError as e: platform = properties.VALUES.run.platform.Get() error_msg = 'Deployment endpoint was not found.' if platform == 'gke': all_clusters = global_methods.ListClusters() clusters = ['* {} in {}'.format(c.name, c.zone) for c in all_clusters] error_msg += (' Perhaps the provided cluster was invalid or ' 'does not have Cloud Run enabled. Pass the ' '`--cluster` and `--cluster-location` flags or set the ' '`run/cluster` and `run/cluster_location` properties to ' 'a valid cluster and zone and retry.' '\nAvailable clusters:\n{}'.format('\n'.join(clusters))) elif platform == 'managed': all_regions = global_methods.ListRegions(self._op_client) if self._region not in all_regions: regions = ['* {}'.format(r) for r in all_regions] error_msg += (' The provided region was invalid. ' 'Pass the `--region` flag or set the ' '`run/region` property to a valid region and retry.' '\nAvailable regions:\n{}'.format('\n'.join(regions))) elif platform == 'kubernetes': error_msg += (' Perhaps the provided cluster was invalid or ' 'does not have Cloud Run enabled. Ensure in your ' 'kubeconfig file that the cluster referenced in ' 'the current context or the specified context ' 'is a valid cluster and retry.') raise serverless_exceptions.DeploymentFailedError(error_msg) except api_exceptions.HttpError as e: platform = properties.VALUES.run.platform.Get() if platform == 'managed': exceptions.reraise(e) k8s_error = serverless_exceptions.KubernetesExceptionParser(e) causes = '\n\n'.join([c['message'] for c in k8s_error.causes]) if not causes: causes = k8s_error.error raise serverless_exceptions.KubernetesError('Error{}:\n{}\n'.format( 's' if len(k8s_error.causes) > 1 else '', causes))
def Run(self, args): """Create or Update service from YAML.""" conn_context = connection_context.GetConnectionContext( args, flags.Product.RUN, self.ReleaseTrack()) with serverless_operations.Connect(conn_context) as client: try: new_service = service.Service( messages_util.DictToMessageWithErrorCheck( args.FILE, client.messages_module.Service), client.messages_module) except messages_util.ScalarTypeMismatchError as e: exceptions.MaybeRaiseCustomFieldMismatch(e) # If managed, namespace must match project (or will default to project if # not specified). # If not managed, namespace simply must not conflict if specified in # multiple places (or will default to "default" if not specified). namespace = args.CONCEPTS.namespace.Parse().Name( ) # From flag or default if new_service.metadata.namespace is not None: if (args.IsSpecified('namespace') and namespace != new_service.metadata.namespace): raise exceptions.ConfigurationError( 'Namespace specified in file does not match passed flag.' ) namespace = new_service.metadata.namespace if flags.GetPlatform() == flags.PLATFORM_MANAGED: project = properties.VALUES.core.project.Get() project_number = projects_util.GetProjectNumber(project) if namespace != project and namespace != str( project_number): raise exceptions.ConfigurationError( 'Namespace must be project ID [{}] or quoted number [{}] for ' 'Cloud Run (fully managed).'.format( project, project_number)) new_service.metadata.namespace = namespace changes = [ config_changes.ReplaceServiceChange(new_service), config_changes.SetLaunchStageAnnotationChange( self.ReleaseTrack()) ] service_ref = resources.REGISTRY.Parse( new_service.metadata.name, params={'namespacesId': new_service.metadata.namespace}, collection='run.namespaces.services') service_obj = client.GetService(service_ref) pretty_print.Info( run_messages_util.GetStartDeployMessage( conn_context, service_ref, operation='Applying new configuration')) deployment_stages = stages.ServiceStages() header = ('Deploying...' if service_obj else 'Deploying new service...') with progress_tracker.StagedProgressTracker( header, deployment_stages, failure_message='Deployment failed', suppress_output=args.async_) as tracker: service_obj = client.ReleaseService(service_ref, changes, tracker, asyn=args.async_, allow_unauthenticated=None, for_replace=True) if args.async_: pretty_print.Success( 'New configuration for [{{bold}}{serv}{{reset}}] is being applied ' 'asynchronously.'.format(serv=service_obj.name)) else: service_obj = client.GetService(service_ref) pretty_print.Success( 'New configuration has been applied to service ' '[{{bold}}{serv}{{reset}}].\n' 'URL: {{bold}}{url}{{reset}}'.format( serv=service_obj.name, url=service_obj.domain)) return service_obj
def Run(self, args): """Create or Update service from YAML.""" run_messages = apis.GetMessagesModule( global_methods.SERVERLESS_API_NAME, global_methods.SERVERLESS_API_VERSION) service_dict = dict(args.FILE) # Clear the status to make migration from k8s deployments easier. # Since a Deployment status will have several fields that Cloud Run doesn't # support, trying to convert it to a message as-is will fail even though # status is ignored by the server. if 'status' in service_dict: del service_dict['status'] # For cases where YAML contains the project number as metadata.namespace, # preemptively convert them to a string to avoid validation failures. namespace = service_dict.get('metadata', {}).get('namespace', None) if namespace is not None and not isinstance(namespace, str): service_dict['metadata']['namespace'] = str(namespace) try: raw_service = messages_util.DictToMessageWithErrorCheck( service_dict, run_messages.Service) new_service = service.Service(raw_service, run_messages) except messages_util.ScalarTypeMismatchError as e: exceptions.MaybeRaiseCustomFieldMismatch( e, help_text= 'Please make sure that the YAML file matches the Knative ' 'service definition spec in https://kubernetes.io/docs/' 'reference/kubernetes-api/services-resources/service-v1/' '#Service.') # If managed, namespace must match project (or will default to project if # not specified). # If not managed, namespace simply must not conflict if specified in # multiple places (or will default to "default" if not specified). namespace = args.CONCEPTS.namespace.Parse().Name( ) # From flag or default if new_service.metadata.namespace is not None: if (args.IsSpecified('namespace') and namespace != new_service.metadata.namespace): raise exceptions.ConfigurationError( 'Namespace specified in file does not match passed flag.') namespace = new_service.metadata.namespace if platforms.GetPlatform() == platforms.PLATFORM_MANAGED: project = properties.VALUES.core.project.Get() project_number = projects_util.GetProjectNumber(project) if namespace != project and namespace != str(project_number): raise exceptions.ConfigurationError( 'Namespace must be project ID [{}] or quoted number [{}] for ' 'Cloud Run (fully managed).'.format( project, project_number)) new_service.metadata.namespace = namespace changes = [ config_changes.ReplaceServiceChange(new_service), config_changes.SetLaunchStageAnnotationChange(self.ReleaseTrack()) ] service_ref = resources.REGISTRY.Parse( new_service.metadata.name, params={'namespacesId': new_service.metadata.namespace}, collection='run.namespaces.services') region_label = new_service.region if new_service.is_managed else None conn_context = connection_context.GetConnectionContext( args, flags.Product.RUN, self.ReleaseTrack(), region_label=region_label) with serverless_operations.Connect(conn_context) as client: service_obj = client.GetService(service_ref) pretty_print.Info( run_messages_util.GetStartDeployMessage( conn_context, service_ref, operation='Applying new configuration to')) deployment_stages = stages.ServiceStages() header = ('Deploying...' if service_obj else 'Deploying new service...') with progress_tracker.StagedProgressTracker( header, deployment_stages, failure_message='Deployment failed', suppress_output=args.async_) as tracker: service_obj = client.ReleaseService(service_ref, changes, tracker, asyn=args.async_, allow_unauthenticated=None, for_replace=True) if args.async_: pretty_print.Success( 'New configuration for [{{bold}}{serv}{{reset}}] is being applied ' 'asynchronously.'.format(serv=service_obj.name)) else: service_obj = client.GetService(service_ref) pretty_print.Success( 'New configuration has been applied to service ' '[{{bold}}{serv}{{reset}}].\n' 'URL: {{bold}}{url}{{reset}}'.format( serv=service_obj.name, url=service_obj.domain)) return service_obj