Ejemplo n.º 1
0
  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
Ejemplo n.º 2
0
 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)
Ejemplo n.º 3
0
 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)
Ejemplo n.º 4
0
  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
Ejemplo n.º 5
0
  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
Ejemplo n.º 6
0
  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)
Ejemplo n.º 7
0
  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))
Ejemplo n.º 8
0
    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