def WithServiceYaml(self, yaml_path): """Overrides settings with service.yaml and returns a new Settings object.""" yaml_dict = yaml.load_path(yaml_path) message = messages_util.DictToMessageWithErrorCheck( yaml_dict, RUN_MESSAGES_MODULE.Service) knative_service = k8s_service.Service(message, RUN_MESSAGES_MODULE) replacements = {} # Planned attributes in # http://doc/1ah6LB9we-FSEhcBZ7_4XQlnOPClTyyQW_O3Q5WNUuJc#bookmark=id.j3st2l8a3s19 try: [container] = knative_service.spec.template.spec.containers except ValueError: raise exceptions.Error( 'knative Service must have exactly one container.') for var in container.env: replacements.setdefault('env_vars', {})[var.name] = var.value service_account_name = knative_service.spec.template.spec.serviceAccountName if service_account_name: replacements['credential'] = ServiceAccountSetting( name=service_account_name) return self.replace(**replacements)
def ListServices(self, namespace_ref): messages = self._messages_module request = messages.RunNamespacesServicesListRequest( parent=namespace_ref.RelativeName()) with metrics.RecordDuration(metric_names.LIST_SERVICES): response = self._client.namespaces_services.List(request) return [service.Service(item, messages) for item in response.items]
def ListServices(client, locations): """Get the global services for a OnePlatform project. Args: client: (base_api.BaseApiClient), instance of a client to use for the list request. locations: (str), The relative name of the locations resource with either an actual location name e.g. 'projects/my-project/locations/us-central1) to query the specified location 'or a wildcard name, '-' (e.g. 'projects/my-project/locations/-') to query all locations. Returns: List of googlecloudsdk.api_lib.run import service.Service objects. """ request = client.MESSAGES_MODULE.RunProjectsLocationsServicesListRequest( parent=locations) response = client.projects_locations_services.List(request) # Log the regions that did not respond. if response.unreachable: log.warning('The following Cloud Run regions did not respond: {}. ' 'List results may be incomplete.'.format(', '.join( sorted(response.unreachable)))) return [ service.Service(item, client.MESSAGES_MODULE) for item in response.items ]
def Run(self, args): """Create or Update service from YAML.""" conn_context = connection_context.GetConnectionContext( args, self.ReleaseTrack()) with serverless_operations.Connect(conn_context) as client: new_service = service.Service( messages_util.DictToMessageWithErrorCheck( args.FILE, client.messages_module.Service), client.messages_module) # 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 project = properties.VALUES.core.project.Get() if flags.IsManaged(args) and namespace != project: raise exceptions.ConfigurationError( 'Namespace must be [{}] for Cloud Run (fully managed).'.format( project)) new_service.metadata.namespace = namespace changes = [config_changes.ReplaceServiceChange(new_service)] service_ref = resources.REGISTRY.Parse( new_service.metadata.name, params={'namespacesId': namespace}, collection='run.namespaces.services') original_service = client.GetService(service_ref) pretty_print.Info(deploy.GetStartDeployMessage(conn_context, service_ref)) deployment_stages = stages.ServiceStages() header = ( 'Deploying...' if original_service else 'Deploying new service...') with progress_tracker.StagedProgressTracker( header, deployment_stages, failure_message='Deployment failed', suppress_output=args.async_) as tracker: client.ReleaseService( service_ref, changes, tracker, asyn=args.async_, allow_unauthenticated=None, for_replace=True) if args.async_: pretty_print.Success( 'Service [{{bold}}{serv}{{reset}}] is deploying ' 'asynchronously.'.format(serv=service_ref.servicesId)) else: pretty_print.Success(deploy.GetSuccessMessageForSynchronousDeploy( client, service_ref))
def ListServices(client, region=_ALL_REGIONS): """Get the global services for a OnePlatform project. Args: client: (base_api.BaseApiClient), instance of a client to use for the list request. region: (str) optional name of location to search for clusters in. If not passed, this defaults to the global value for all locations. Returns: List of googlecloudsdk.api_lib.run import service.Service objects. """ project = properties.VALUES.core.project.Get(required=True) locations = resources.REGISTRY.Parse(region, params={'projectsId': project}, collection='run.projects.locations') request = client.MESSAGES_MODULE.RunProjectsLocationsServicesListRequest( parent=locations.RelativeName()) response = client.projects_locations_services.List(request) # Log the regions that did not respond. if response.unreachable: log.warning('The following Cloud Run regions did not respond: {}. ' 'List results may be incomplete.'.format(', '.join( sorted(response.unreachable)))) return [ service.Service(item, client.MESSAGES_MODULE) for item in response.items ]
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.HttpNotFoundError: return None
def Run(self, args): """Create or Update service from YAML.""" conn_context = connection_context.GetConnectionContext(args) if conn_context.supports_one_platform: flags.VerifyOnePlatformFlags(args) else: flags.VerifyGKEFlags(args) with serverless_operations.Connect(conn_context) as client: message_dict = yaml.load_path(args.FILE) new_service = service.Service( messages_util.DictToMessageWithErrorCheck( message_dict, client.messages_module.Service), client.messages_module) changes = [config_changes.ReplaceServiceChange(new_service)] service_ref = resources.REGISTRY.Parse( new_service.metadata.name, params={'namespacesId': new_service.metadata.namespace}, collection='run.namespaces.services') original_service = client.GetService(service_ref) pretty_print.Info( deploy.GetStartDeployMessage(conn_context, service_ref)) deployment_stages = stages.ServiceStages() header = ('Deploying...' if original_service else 'Deploying new service...') with progress_tracker.StagedProgressTracker( header, deployment_stages, failure_message='Deployment failed', suppress_output=args. async) as tracker: client.ReleaseService(service_ref, changes, tracker, asyn=args. async, allow_unauthenticated=None, for_replace=True) if args. async: pretty_print.Success( 'Service [{{bold}}{serv}{{reset}}] is deploying ' 'asynchronously.'.format(serv=service_ref.servicesId)) else: pretty_print.Success( deploy.GetSuccessMessageForSynchronousDeploy( client, service_ref))
def ListServices(locations): """Get the global services for a OnePlatform project. Args: locations: (str), The relative name of the locations resource with either an actual location name e.g. 'projects/my-project/locations/us-central1) to query the specified location 'or a wildcard name, '-' (e.g. 'projects/my-project/locations/-') to query all locations. Returns: List of googlecloudsdk.api_lib.run import service.Service objects. """ client = apis.GetClientInstance(SERVERLESS_API_NAME, SERVERLESS_API_VERSION) request = client.MESSAGES_MODULE.RunProjectsLocationsServicesListRequest( parent=locations ) response = client.projects_locations_services.List(request) return [service.Service( item, client.MESSAGES_MODULE) for item in response.items]
def ListServices(client, locations): """Get the global services for a OnePlatform project. Args: client: (base_api.BaseApiClient), instance of a client to use for the list request. locations: (str), The relative name of the locations resource with either an actual location name e.g. 'projects/my-project/locations/us-central1) to query the specified location 'or a wildcard name, '-' (e.g. 'projects/my-project/locations/-') to query all locations. Returns: List of googlecloudsdk.api_lib.run import service.Service objects. """ request = client.MESSAGES_MODULE.RunProjectsLocationsServicesListRequest( parent=locations) response = client.projects_locations_services.List(request) return [ service.Service(item, client.MESSAGES_MODULE) for item in response.items ]
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.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 _UpdateOrCreateService(self, service_ref, config_changes, with_code, private_endpoint=None): """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. private_endpoint: bool, True if creating a new Service for Cloud Run on GKE that should only be addressable from within the cluster. False if it should be publicly addressable. None if its existing visibility should remain unchanged. Returns: The Service object we created or modified. """ nonce = _Nonce() config_changes = [_NewRevisionForcingChange(nonce)] + config_changes messages = self._messages_module # GET the Service serv = self.GetService(service_ref) try: if serv: if not with_code: # Avoid changing the running code by making the new revision by digest self._EnsureImageDigest(serv, config_changes) if private_endpoint is None: # Don't change the existing service visibility pass elif private_endpoint: serv.labels[service.ENDPOINT_VISIBILITY] = service.CLUSTER_LOCAL else: del serv.labels[service.ENDPOINT_VISIBILITY] # PUT the changed Service for config_change in config_changes: config_change.AdjustConfiguration(serv.configuration, serv.metadata) serv_name = service_ref.RelativeName() serv_update_req = ( messages.RunNamespacesServicesReplaceServiceRequest( service=serv.Message(), name=serv_name)) with metrics.record_duration(metrics.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, private_endpoint) new_serv.name = service_ref.servicesId pretty_print.Info('Creating new service [{bold}{service}{reset}]', service=new_serv.name) parent = service_ref.Parent().RelativeName() for config_change in config_changes: config_change.AdjustConfiguration(new_serv.configuration, new_serv.metadata) serv_create_req = ( messages.RunNamespacesServicesCreateRequest( service=new_serv.Message(), parent=parent)) with metrics.record_duration(metrics.CREATE_SERVICE): raw_service = self._client.namespaces_services.Create( serv_create_req) return service.Service(raw_service, messages) except api_exceptions.HttpBadRequestError as e: error_payload = exceptions_util.HttpErrorPayload(e) if error_payload.field_violations: if (serverless_exceptions.BadImageError.IMAGE_ERROR_FIELD in error_payload.field_violations): exceptions.reraise(serverless_exceptions.BadImageError(e)) exceptions.reraise(e) except api_exceptions.HttpNotFoundError as e: # TODO(b/118339293): List available regions to check whether provided # region is invalid or not. raise serverless_exceptions.DeploymentFailedError( 'Deployment endpoint was not found. Perhaps the provided ' 'region was invalid. Set the `run/region` property to a valid ' 'region and retry. Ex: `gcloud config set run/region us-central1`')
def _UpdateOrCreateService(self, service_ref, config_changes, with_code): """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: boolean, True if the config_changes contains code to deploy. We can't create the service if we're not deploying code. Returns: The Service object we created or modified. """ nonce = _Nonce() config_changes = [_NewRevisionForcingChange(nonce)] + config_changes messages = self._messages_module # GET the Service serv = self.GetService(service_ref) try: if serv: if not with_code: # Avoid changing the running code by making the new revision by digest self._EnsureImageDigest(serv, config_changes) # PUT the changed Service for config_change in config_changes: config_change.AdjustConfiguration(serv.configuration, serv.metadata) serv_name = service_ref.RelativeName() serv_update_req = ( messages.ServerlessNamespacesServicesReplaceServiceRequest( service=serv.Message(), name=serv_name)) with metrics.record_duration(metrics.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 pretty_print.Info( 'Creating new service [{bold}{service}{reset}]', service=new_serv.name) parent = service_ref.Parent().RelativeName() for config_change in config_changes: config_change.AdjustConfiguration(new_serv.configuration, new_serv.metadata) serv_create_req = ( messages.ServerlessNamespacesServicesCreateRequest( service=new_serv.Message(), parent=parent)) with metrics.record_duration(metrics.CREATE_SERVICE): raw_service = self._client.namespaces_services.Create( serv_create_req) return service.Service(raw_service, messages) except api_exceptions.HttpBadRequestError as e: error_payload = exceptions_util.HttpErrorPayload(e) if error_payload.field_violations: if (serverless_exceptions.BadImageError.IMAGE_ERROR_FIELD in error_payload.field_violations): exceptions.reraise(serverless_exceptions.BadImageError(e)) exceptions.reraise(e)
def RunServiceRevisionTemplate(msg, msg_module): svc = run_v1_messages.Service() svc.spec = run_v1_messages.ServiceSpec() svc.spec.template = msg return service.Service(svc, msg_module).template
def RunService(msg_module, metadata=None, spec=None, status=None): svc = run_v1_messages.Service() svc.metadata = metadata svc.spec = spec svc.status = status return service.Service(svc, msg_module)
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 _UpdateOrCreateService(self, service_ref, config_changes, with_code): """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. Returns: The Service object we created or modified. """ nonce = _Nonce() config_changes = [ _NewRevisionForcingChange(nonce), _SetClientNameAndVersion() ] + config_changes messages = self._messages_module # GET the Service serv = self.GetService(service_ref) try: if serv: if not with_code: # Avoid changing the running code by making the new revision by digest self._EnsureImageDigest(serv, config_changes) # Revision names must be unique across the namespace. # To prevent the revision name being unchanged from the last revision, # we reset the value so the default naming scheme will be used instead. serv.template.name = None # PUT the changed Service for config_change in config_changes: 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: 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.HttpBadRequestError as e: error_payload = exceptions_util.HttpErrorPayload(e) if error_payload.field_violations: if (serverless_exceptions.BadImageError.IMAGE_ERROR_FIELD in error_payload.field_violations): exceptions.reraise(serverless_exceptions.BadImageError(e)) elif (serverless_exceptions.MalformedLabelError. LABEL_ERROR_FIELD in error_payload.field_violations): exceptions.reraise( serverless_exceptions.MalformedLabelError(e)) exceptions.reraise(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: k8s_error = serverless_exceptions.KubernetesExceptionParser(e) raise serverless_exceptions.KubernetesError( 'Error{}:\n{}\n'.format( 's' if len(k8s_error.causes) > 1 else '', '\n\n'.join([c['message'] for c in k8s_error.causes])))
def _UpdateOrCreateService(self, service_ref, config_changes, with_code, private_endpoint=None): """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. private_endpoint: bool, True if creating a new Service for Cloud Run on GKE that should only be addressable from within the cluster. False if it should be publicly addressable. None if its existing visibility should remain unchanged. Returns: The Service object we created or modified. """ nonce = _Nonce() config_changes = [_NewRevisionForcingChange(nonce)] + config_changes messages = self._messages_module # GET the Service serv = self.GetService(service_ref) try: if serv: if not with_code: # Avoid changing the running code by making the new revision by digest self._EnsureImageDigest(serv, config_changes) if private_endpoint is None: # Don't change the existing service visibility pass elif private_endpoint: serv.labels[ service.ENDPOINT_VISIBILITY] = service.CLUSTER_LOCAL else: del serv.labels[service.ENDPOINT_VISIBILITY] # PUT the changed Service for config_change in config_changes: config_change.AdjustConfiguration(serv.configuration, serv.metadata) 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, private_endpoint) new_serv.name = service_ref.servicesId parent = service_ref.Parent().RelativeName() for config_change in config_changes: config_change.AdjustConfiguration(new_serv.configuration, new_serv.metadata) 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.HttpBadRequestError as e: error_payload = exceptions_util.HttpErrorPayload(e) if error_payload.field_violations: if (serverless_exceptions.BadImageError.IMAGE_ERROR_FIELD in error_payload.field_violations): exceptions.reraise(serverless_exceptions.BadImageError(e)) exceptions.reraise(e) except api_exceptions.HttpNotFoundError as e: error_msg = 'Deployment endpoint was not found.' if not self._region: 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))) else: 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))) raise serverless_exceptions.DeploymentFailedError(error_msg)
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