Ejemplo n.º 1
0
 def RunCommand(self, args, **kwargs):
     try:
         project = properties.VALUES.core.project.GetOrFail()
         return CreateFeature(project, self.FEATURE_NAME,
                              self.FEATURE_DISPLAY_NAME, **kwargs)
     except apitools_exceptions.HttpUnauthorizedError as e:
         raise exceptions.Error(
             'You are not authorized to enable {} Feature from project [{}]. '
             'Underlying error: {}'.format(self.FEATURE_DISPLAY_NAME,
                                           project, e))
     except properties.RequiredPropertyError as e:
         raise exceptions.Error('Failed to retrieve the project ID.')
     except apitools_exceptions.HttpConflictError as e:
         # If the error is not due to the object already existing, re-raise.
         error = core_api_exceptions.HttpErrorPayload(e)
         if error.status_description != 'ALREADY_EXISTS':
             raise
         else:
             log.status.Print(
                 '{} Feature for project [{}] is already enabled'.format(
                     self.FEATURE_DISPLAY_NAME, project))
     except apitools_exceptions.HttpBadRequestError as e:
         error = core_api_exceptions.HttpErrorPayload(e)
         if error.status_description != 'FAILED_PRECONDITION':
             raise
         else:
             log.status.Print(error.status_message)
Ejemplo n.º 2
0
    def WaitForOperation(self, operation, retry_callback=None):
        """Wait until the operation is complete or times out.

    This does not use the core api_lib.util.waiter because the cloud build logs
    serve as a progress tracker.

    Args:
      operation: The operation resource to wait on
      retry_callback: A callback to be executed before each retry, if desired.
    Returns:
      The operation resource when it has completed
    Raises:
      OperationTimeoutError: when the operation polling times out
      OperationError: when the operation completed with an error
    """

        completed_operation = self._PollUntilDone(operation, retry_callback)
        if not completed_operation:
            raise OperationTimeoutError(
                ('Operation [{0}] timed out. This operation '
                 'may still be underway.').format(operation.name))

        if completed_operation.error:
            message = exceptions.HttpErrorPayload(
                completed_operation.error).format(_ERROR_FORMAT_STRING)

            raise OperationError(message)

        return completed_operation
 def Enable(self, feature):
     project = properties.VALUES.core.project.GetOrFail()
     enable_api.EnableServiceIfDisabled(project, self.feature.api)
     parent = util.LocationResourceName(project)
     try:
         # Retry if we still get "API not activated"; it can take a few minutes
         # for Chemist to catch up. See b/28800908.
         # TODO(b/177098463): Add a spinner here?
         retryer = retry.Retryer(max_retrials=4,
                                 exponential_sleep_multiplier=1.75)
         op = retryer.RetryOnException(
             self.hubclient.CreateFeature,
             args=(parent, self.feature_name, feature),
             should_retry_if=self._FeatureAPINotEnabled,
             sleep_ms=1000)
     except retry.MaxRetrialsException:
         raise exceptions.Error(
             'Retry limit exceeded waiting for {} to enable'.format(
                 self.feature.api))
     except apitools_exceptions.HttpConflictError as e:
         # If the error is not due to the object already existing, re-raise.
         error = core_api_exceptions.HttpErrorPayload(e)
         if error.status_description != 'ALREADY_EXISTS':
             raise
         # TODO(b/177098463): Decide if this should be a hard error if a spec was
         # set, but not applied, because the Feature already existed.
         log.status.Print(
             '{} Feature for project [{}] is already enabled'.format(
                 self.feature.display_name, project))
         return
     msg = 'Waiting for Feature {} to be created'.format(
         self.feature.display_name)
     return self.WaitForHubOp(self.hubclient.feature_waiter,
                              op=op,
                              message=msg)
 def _FeatureAPINotEnabled(self, exc_type, exc_value, traceback, state):
     del traceback, state  # Unused
     if exc_type != apitools_exceptions.HttpBadRequestError:
         return False
     error = core_api_exceptions.HttpErrorPayload(exc_value)
     # TODO(b/188807249): Add a reference to this error in the error package.
     if not (error.status_description == 'FAILED_PRECONDITION'
             and self.feature.api in error.message
             and 'is not enabled' in error.message):
         return False
     log.status.Print('Waiting for service API enablement to finish...')
     return True
Ejemplo n.º 5
0
def _GetViolationsFromError(error):
    """Looks for violations descriptions in error message.

  Args:
    error: HttpError containing error information.
  Returns:
    String of newline-separated violations descriptions.
  """
    error_payload = exceptions_util.HttpErrorPayload(error)
    field_errors = error_payload.field_violations
    if not field_errors:
        return ''
    return '\n'.join(field_errors.values()) + '\n'
Ejemplo n.º 6
0
def _GetViolationsFromError(error):
  """Looks for violations descriptions in error message.

  Args:
    error: HttpError containing error information.
  Returns:
    String of newline-separated violations descriptions.
  """
  error_payload = exceptions_util.HttpErrorPayload(error)
  errors = []
  errors.extend(
      ['{}:\n{}'.format(k, v) for k, v in error_payload.violations.items()])
  errors.extend([
      '{}:\n{}'.format(k, v) for k, v in error_payload.field_violations.items()
  ])
  if errors:
    return '\n'.join(errors) + '\n'
  return ''
Ejemplo n.º 7
0
  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`')
Ejemplo n.º 8
0
 def _GetViolations(self, err):
   payload = exceptions.HttpErrorPayload(err)
   return payload.violations
Ejemplo n.º 9
0
 def _GetFieldViolations(self, err):
   payload = exceptions.HttpErrorPayload(err)
   return payload.field_violations
Ejemplo n.º 10
0
    def Run(self, args):
        project = arg_utils.GetFromNamespace(args,
                                             '--project',
                                             use_defaults=True)

        # This incidentally verifies that the kubeconfig and context args are valid.
        kube_client = hub_util.KubernetesClient(args)
        uuid = hub_util.GetClusterUUID(kube_client)

        self._VerifyClusterExclusivity(kube_client, project, args.context,
                                       uuid)

        # Read the service account files provided in the arguments early, in order
        # to catch invalid files before performing mutating operations.
        try:
            service_account_key_data = hub_util.Base64EncodedFileContents(
                args.service_account_key_file)
        except files.Error as e:
            raise exceptions.Error('Could not process {}: {}'.format(
                SERVICE_ACCOUNT_KEY_FILE_FLAG, e))

        docker_credential_data = None
        if args.docker_credential_file:
            try:
                docker_credential_data = hub_util.Base64EncodedFileContents(
                    args.docker_credential_file)
            except files.Error as e:
                raise exceptions.Error('Could not process {}: {}'.format(
                    DOCKER_CREDENTIAL_FILE_FLAG, e))

        # The full resource name of the membership for this registration flow.
        name = 'projects/{}/locations/global/memberships/{}'.format(
            project, uuid)

        # Attempt to create a membership.
        already_exists = False
        try:
            hub_util.ApplyMembershipResources(kube_client, project)
            obj = hub_util.CreateMembership(project, uuid, args.CLUSTER_NAME)
        except apitools_exceptions.HttpConflictError as e:
            # If the error is not due to the object already existing, re-raise.
            error = core_api_exceptions.HttpErrorPayload(e)
            if error.status_description != 'ALREADY_EXISTS':
                raise

            # The membership already exists. Check to see if it has the same
            # description (i.e., user-visible cluster name).
            obj = hub_util.GetMembership(name)
            if obj.description != args.CLUSTER_NAME:
                # A membership exists, but does not have the same description. This is
                # possible if two different users attempt to register the same
                # cluster, or if the user is upgrading and has passed a different
                # cluster name. Treat this as an error: even in the upgrade case,
                # this is useful to prevent the user from upgrading the wrong cluster.
                raise exceptions.Error(
                    'There is an existing membership, [{}], that conflicts with [{}]. '
                    'Please delete it before continuing:\n\n'
                    '  gcloud {}container memberships delete {}'.format(
                        obj.description, args.CLUSTER_NAME,
                        hub_util.ReleaseTrackCommandPrefix(
                            self.ReleaseTrack()), name))

            # The membership exists and has the same description.
            already_exists = True
            console_io.PromptContinue(
                message='A membership for [{}] already exists. Continuing will '
                'update the Connect agent deployment to use a new image (if one is '
                'available), or install the Connect agent if it is not already '
                'running.'.format(args.CLUSTER_NAME),
                cancel_on_no=True)

        # A membership exists. Attempt to update the existing agent deployment, or
        # install a new agent if necessary.
        if already_exists:
            obj = hub_util.GetMembership(name)
            hub_util.DeployConnectAgent(args,
                                        service_account_key_data,
                                        docker_credential_data,
                                        upgrade=True)
            return obj

        # No membership exists. Attempt to create a new one, and install a new
        # agent.
        try:
            hub_util.DeployConnectAgent(args,
                                        service_account_key_data,
                                        docker_credential_data,
                                        upgrade=False)
        except:
            hub_util.DeleteMembership(name)
            hub_util.DeleteMembershipResources(kube_client)
            raise
        return 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: 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)
Ejemplo n.º 12
0
    def Run(self, args):
        project = arg_utils.GetFromNamespace(args,
                                             '--project',
                                             use_defaults=True)
        # This incidentally verifies that the kubeconfig and context args are valid.
        with kube_util.KubernetesClient(args) as kube_client:
            kube_client.CheckClusterAdminPermissions()
            kube_util.ValidateClusterIdentifierFlags(kube_client, args)
            uuid = kube_util.GetClusterUUID(kube_client)
            # Read the service account files provided in the arguments early, in order
            # to catch invalid files before performing mutating operations.
            # Service Account key file is required if Workload Identity is not
            # enabled.
            # If Workload Identity is enabled, then the Connect Agent uses
            # a Kubernetes Service Account token instead and hence a GCP Service
            # Account key is not required.
            service_account_key_data = ''
            if args.service_account_key_file:
                try:
                    service_account_key_data = hub_util.Base64EncodedFileContents(
                        args.service_account_key_file)
                except files.Error as e:
                    raise exceptions.Error('Could not process {}: {}'.format(
                        SERVICE_ACCOUNT_KEY_FILE_FLAG, e))

            docker_credential_data = None
            if args.docker_credential_file:
                try:
                    docker_credential_data = hub_util.Base64EncodedFileContents(
                        args.docker_credential_file)
                except files.Error as e:
                    raise exceptions.Error('Could not process {}: {}'.format(
                        DOCKER_CREDENTIAL_FILE_FLAG, e))

            gke_cluster_self_link = kube_client.processor.gke_cluster_self_link

            issuer_url = None
            # enable_workload_identity, public_issuer_url, and
            # manage_workload_identity_bucket are only properties if we are on the
            # alpha track
            if (self.ReleaseTrack() is base.ReleaseTrack.ALPHA
                    and args.enable_workload_identity):
                if args.public_issuer_url:
                    issuer_url = args.public_issuer_url
                    # Use the user-provided public URL, and ignore the built-in endpoints.
                    try:
                        openid_config_json = kube_client.GetOpenIDConfiguration(
                            issuer_url=args.public_issuer_url)
                    except Exception as e:  # pylint: disable=broad-except
                        raise exceptions.Error(
                            'Please double check that --public-issuer-url was set '
                            'correctly: {}'.format(e))
                else:
                    # Since the user didn't specify a public URL, try to use the cluster's
                    # built-in endpoints.
                    try:
                        openid_config_json = kube_client.GetOpenIDConfiguration(
                        )
                    except Exception as e:  # pylint: disable=broad-except
                        raise exceptions.Error(
                            'Please double check that it is possible to access the '
                            '/.well-known/openid-configuration endpoint on the cluster: '
                            '{}'.format(e))

                # Extract the issuer URL from the discovery doc.
                issuer_url = json.loads(openid_config_json).get('issuer')
                if not issuer_url:
                    raise exceptions.Error(
                        'Invalid OpenID Config: '
                        'missing issuer: {}'.format(openid_config_json))
                # If a public issuer URL was provided, ensure it matches what came back
                # in the discovery doc.
                elif args.public_issuer_url \
                    and args.public_issuer_url != issuer_url:
                    raise exceptions.Error(
                        '--public-issuer-url {} did not match issuer '
                        'returned in discovery doc: {}'.format(
                            args.public_issuer_url, issuer_url))

                # Set up the GCS bucket that serves OpenID Provider Config and JWKS.
                if args.manage_workload_identity_bucket:
                    openid_keyset_json = kube_client.GetOpenIDKeyset()
                    api_util.CreateWorkloadIdentityBucket(
                        project, issuer_url, openid_config_json,
                        openid_keyset_json)

            # Attempt to create a membership.
            already_exists = False

            obj = None
            # For backward compatiblity, check if a membership was previously created
            # using the cluster uuid.
            parent = api_util.ParentRef(project, 'global')
            membership_id = uuid
            resource_name = api_util.MembershipRef(project, 'global', uuid)
            obj = self._CheckMembershipWithUUID(resource_name,
                                                args.CLUSTER_NAME)
            if obj:
                # The membership exists and has the same description.
                already_exists = True
            else:
                # Attempt to create a new membership using cluster_name.
                membership_id = args.CLUSTER_NAME
                resource_name = api_util.MembershipRef(project, 'global',
                                                       args.CLUSTER_NAME)
                try:
                    self._VerifyClusterExclusivity(kube_client, parent,
                                                   membership_id)
                    obj = api_util.CreateMembership(project, args.CLUSTER_NAME,
                                                    args.CLUSTER_NAME,
                                                    gke_cluster_self_link,
                                                    uuid, self.ReleaseTrack(),
                                                    issuer_url)
                except apitools_exceptions.HttpConflictError as e:
                    # If the error is not due to the object already existing, re-raise.
                    error = core_api_exceptions.HttpErrorPayload(e)
                    if error.status_description != 'ALREADY_EXISTS':
                        raise
                    obj = api_util.GetMembership(resource_name,
                                                 self.ReleaseTrack())
                    if not obj.externalId:
                        raise exceptions.Error(
                            'invalid membership {} does not have '
                            'external_id field set. We cannot determine '
                            'if registration is requested against a '
                            'valid existing Membership. Consult the '
                            'documentation on container hub memberships '
                            'update for more information or run gcloud '
                            'container hub memberships delete {} if you '
                            'are sure that this is an invalid or '
                            'otherwise stale Membership'.format(
                                membership_id, membership_id))
                    if obj.externalId != uuid:
                        raise exceptions.Error(
                            'membership {} already exists in the project'
                            ' with another cluster. If this operation is'
                            ' intended, please run `gcloud container '
                            'hub memberships delete {}` and register '
                            'again.'.format(membership_id, membership_id))

                    # The membership exists with same cluster_name.
                    already_exists = True

            # In case of an existing membership, check with the user to upgrade the
            # Connect-Agent.
            if already_exists:
                console_io.PromptContinue(
                    message=
                    'A membership [{}] for the cluster [{}] already exists. '
                    'Continuing will reinstall the Connect agent deployment to use a '
                    'new image (if one is available).'.format(
                        resource_name, args.CLUSTER_NAME),
                    cancel_on_no=True)
            else:
                log.status.Print(
                    'Created a new membership [{}] for the cluster [{}]'.
                    format(resource_name, args.CLUSTER_NAME))

            # Attempt to update the existing agent deployment, or install a new agent
            # if necessary.
            try:
                self._InstallOrUpdateExclusivityArtifacts(
                    kube_client, resource_name)
                agent_util.DeployConnectAgent(kube_client, args,
                                              service_account_key_data,
                                              docker_credential_data,
                                              resource_name,
                                              self.ReleaseTrack())
            except Exception as e:
                log.status.Print(
                    'Error in installing the Connect Agent: {}'.format(e))
                # In case of a new membership, we need to clean up membership and
                # resources if we failed to install the Connect Agent.
                if not already_exists:
                    api_util.DeleteMembership(resource_name,
                                              self.ReleaseTrack())
                    exclusivity_util.DeleteMembershipResources(kube_client)
                raise
            log.status.Print(
                'Finished registering the cluster [{}] with the Hub.'.format(
                    args.CLUSTER_NAME))
            return obj
Ejemplo n.º 13
0
    def Run(self, args):
        project = arg_utils.GetFromNamespace(args,
                                             '--project',
                                             use_defaults=True)
        # This incidentally verifies that the kubeconfig and context args are valid.
        with kube_util.KubernetesClient(args) as kube_client:
            kube_client.CheckClusterAdminPermissions()
            kube_util.ValidateClusterIdentifierFlags(kube_client, args)
            uuid = kube_util.GetClusterUUID(kube_client)
            # Read the service account files provided in the arguments early, in order
            # to catch invalid files before performing mutating operations.
            try:
                service_account_key_data = hub_util.Base64EncodedFileContents(
                    args.service_account_key_file)
            except files.Error as e:
                raise exceptions.Error('Could not process {}: {}'.format(
                    SERVICE_ACCOUNT_KEY_FILE_FLAG, e))

            docker_credential_data = None
            if args.docker_credential_file:
                try:
                    docker_credential_data = hub_util.Base64EncodedFileContents(
                        args.docker_credential_file)
                except files.Error as e:
                    raise exceptions.Error('Could not process {}: {}'.format(
                        DOCKER_CREDENTIAL_FILE_FLAG, e))

            gke_cluster_self_link = kube_client.processor.gke_cluster_self_link

            issuer_url = None
            # public_issuer_url is only a property if we are on the alpha track
            if self.ReleaseTrack() is base.ReleaseTrack.ALPHA and \
                args.public_issuer_url:
                issuer_url = args.public_issuer_url

            # Attempt to create a membership.
            already_exists = False

            obj = None
            # For backward compatiblity, check if a membership was previously created
            # using the cluster uuid.
            parent = api_util.ParentRef(project, 'global')
            membership_id = uuid
            resource_name = api_util.MembershipRef(project, 'global', uuid)
            obj = self._CheckMembershipWithUUID(resource_name,
                                                args.CLUSTER_NAME)
            if obj:
                # The membership exists and has the same description.
                already_exists = True
            else:
                # Attempt to create a new membership using cluster_name.
                membership_id = args.CLUSTER_NAME
                resource_name = api_util.MembershipRef(project, 'global',
                                                       args.CLUSTER_NAME)
                try:
                    self._VerifyClusterExclusivity(kube_client, parent,
                                                   membership_id)
                    obj = api_util.CreateMembership(project, args.CLUSTER_NAME,
                                                    args.CLUSTER_NAME,
                                                    gke_cluster_self_link,
                                                    uuid, self.ReleaseTrack(),
                                                    issuer_url)
                except apitools_exceptions.HttpConflictError as e:
                    # If the error is not due to the object already existing, re-raise.
                    error = core_api_exceptions.HttpErrorPayload(e)
                    if error.status_description != 'ALREADY_EXISTS':
                        raise
                    obj = api_util.GetMembership(resource_name,
                                                 self.ReleaseTrack())
                    if not obj.externalId:
                        raise exceptions.Error(
                            'invalid membership {} does not have '
                            'external_id field set. We cannot determine '
                            'if registration is requested against a '
                            'valid existing Membership. Consult the '
                            'documentation on container hub memberships '
                            'update for more information or run gcloud '
                            'container hub memberships delete {} if you '
                            'are sure that this is an invalid or '
                            'otherwise stale Membership'.format(
                                membership_id, membership_id))
                    if obj.externalId != uuid:
                        raise exceptions.Error(
                            'membership {} already exists in the project'
                            ' with another cluster. If this operation is'
                            ' intended, please run `gcloud container '
                            'hub memberships delete {}` and register '
                            'again.'.format(membership_id, membership_id))

                    # The membership exists with same cluster_name.
                    already_exists = True

            # In case of an existing membership, check with the user to upgrade the
            # Connect-Agent.
            if already_exists:
                console_io.PromptContinue(
                    message=
                    'A membership [{}] for the cluster [{}] already exists. '
                    'Continuing will reinstall the Connect agent deployment to use a '
                    'new image (if one is available).'.format(
                        resource_name, args.CLUSTER_NAME),
                    cancel_on_no=True)
            else:
                log.status.Print(
                    'Created a new membership [{}] for the cluster [{}]'.
                    format(resource_name, args.CLUSTER_NAME))

            # Attempt to update the existing agent deployment, or install a new agent
            # if necessary.
            try:
                self._InstallOrUpdateExclusivityArtifacts(
                    kube_client, resource_name)
                agent_util.DeployConnectAgent(kube_client, args,
                                              service_account_key_data,
                                              docker_credential_data,
                                              resource_name,
                                              self.ReleaseTrack())
            except Exception as e:
                log.status.Print(
                    'Error in installing the Connect Agent: {}'.format(e))
                # In case of a new membership, we need to clean up membership and
                # resources if we failed to install the Connect Agent.
                if not already_exists:
                    api_util.DeleteMembership(resource_name,
                                              self.ReleaseTrack())
                    exclusivity_util.DeleteMembershipResources(kube_client)
                raise
            log.status.Print(
                'Finished registering the cluster [{}] with the Hub.'.format(
                    args.CLUSTER_NAME))
            return obj
Ejemplo n.º 14
0
    def Run(self, args):
        project = arg_utils.GetFromNamespace(args,
                                             '--project',
                                             use_defaults=True)

        # This incidentally verifies that the kubeconfig and context args are valid.
        kube_client = kube_util.KubernetesClient(args)
        uuid = kube_util.GetClusterUUID(kube_client)
        gke_cluster_self_link = api_util.GKEClusterSelfLink(args)
        # Read the service account files provided in the arguments early, in order
        # to catch invalid files before performing mutating operations.
        try:
            service_account_key_data = hub_util.Base64EncodedFileContents(
                args.service_account_key_file)
        except files.Error as e:
            raise exceptions.Error('Could not process {}: {}'.format(
                SERVICE_ACCOUNT_KEY_FILE_FLAG, e))

        docker_credential_data = None
        if args.docker_credential_file:
            try:
                docker_credential_data = hub_util.Base64EncodedFileContents(
                    args.docker_credential_file)
            except files.Error as e:
                raise exceptions.Error('Could not process {}: {}'.format(
                    DOCKER_CREDENTIAL_FILE_FLAG, e))

        gke_cluster_self_link = api_util.GKEClusterSelfLink(args)

        # Attempt to create a membership.
        already_exists = False

        obj = None
        # For backward compatiblity, check if a membership was previously created
        # using the cluster uuid.
        parent = api_util.ParentRef(project, 'global')
        membership_id = uuid
        resource_name = api_util.MembershipRef(project, 'global', uuid)
        obj = self._CheckMembershipWithUUID(resource_name, args.CLUSTER_NAME)
        if obj:
            # The membership exists and has the same description.
            already_exists = True
        else:
            # Attempt to create a new membership using cluster_name.
            membership_id = args.CLUSTER_NAME
            resource_name = api_util.MembershipRef(project, 'global',
                                                   args.CLUSTER_NAME)
            try:
                self._VerifyClusterExclusivity(kube_client, parent,
                                               membership_id)
                obj = api_util.CreateMembership(project, args.CLUSTER_NAME,
                                                args.CLUSTER_NAME,
                                                gke_cluster_self_link, uuid,
                                                self.ReleaseTrack())
            except apitools_exceptions.HttpConflictError as e:
                # If the error is not due to the object already existing, re-raise.
                error = core_api_exceptions.HttpErrorPayload(e)
                if error.status_description != 'ALREADY_EXISTS':
                    raise
                # The membership exists with same cluster_name.
                already_exists = True
                obj = api_util.GetMembership(resource_name,
                                             self.ReleaseTrack())

        # In case of an existing membership, check with the user to upgrade the
        # Connect-Agent.
        if already_exists:
            console_io.PromptContinue(
                message='A membership for [{}] already exists. Continuing will '
                'reinstall the Connect agent deployment to use a new image (if one '
                'is available).'.format(resource_name),
                cancel_on_no=True)

        # No membership exists. Attempt to create a new one, and install a new
        # agent.
        try:
            self._InstallOrUpdateExclusivityArtifacts(kube_client,
                                                      resource_name)
            agent_util.DeployConnectAgent(args, service_account_key_data,
                                          docker_credential_data,
                                          resource_name, self.ReleaseTrack())
        except:
            # In case of a new membership, we need to clean up membership and
            # resources if we failed to install the Connect Agent.
            if not already_exists:
                api_util.DeleteMembership(resource_name, self.ReleaseTrack())
                exclusivity_util.DeleteMembershipResources(kube_client)
            raise
        return obj
Ejemplo n.º 15
0
    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)
Ejemplo n.º 16
0
    def Run(self, args):
        project = arg_utils.GetFromNamespace(args,
                                             '--project',
                                             use_defaults=True)
        # This incidentally verifies that the kubeconfig and context args are valid.
        with kube_util.KubernetesClient(args) as kube_client:
            kube_client.CheckClusterAdminPermissions()
            kube_util.ValidateClusterIdentifierFlags(kube_client, args)
            uuid = kube_util.GetClusterUUID(kube_client)
            # Read the service account files provided in the arguments early, in order
            # to catch invalid files before performing mutating operations.
            # Service Account key file is required if Workload Identity is not
            # enabled.
            # If Workload Identity is enabled, then the Connect Agent uses
            # a Kubernetes Service Account token instead and hence a GCP Service
            # Account key is not required.
            service_account_key_data = ''
            if args.service_account_key_file:
                try:
                    service_account_key_data = hub_util.Base64EncodedFileContents(
                        args.service_account_key_file)
                except files.Error as e:
                    raise exceptions.Error('Could not process {}: {}'.format(
                        SERVICE_ACCOUNT_KEY_FILE_FLAG, e))

            docker_credential_data = None
            if args.docker_credential_file:
                try:
                    docker_credential_data = hub_util.Base64EncodedFileContents(
                        args.docker_credential_file)
                except files.Error as e:
                    raise exceptions.Error('Could not process {}: {}'.format(
                        DOCKER_CREDENTIAL_FILE_FLAG, e))

            gke_cluster_self_link = kube_client.processor.gke_cluster_self_link
            issuer_url = None
            private_keyset_json = None
            if args.enable_workload_identity:
                # public_issuer_url can be None or given by user or gke_cluster_uri
                # (incase of a gke cluster).
                # args.public_issuer_url takes precedence over gke_cluster_uri.
                public_issuer_url = args.public_issuer_url or kube_client.processor.gke_cluster_uri or None

                try:
                    openid_config_json = six.ensure_str(
                        kube_client.GetOpenIDConfiguration(
                            issuer_url=public_issuer_url),
                        encoding='utf-8')
                except Exception as e:  # pylint: disable=broad-except
                    raise exceptions.Error(
                        'Error getting the OpenID Provider Configuration: '
                        '{}'.format(e))

                # Extract the issuer URL from the discovery doc.
                issuer_url = json.loads(openid_config_json).get('issuer')
                if not issuer_url:
                    raise exceptions.Error(
                        'Invalid OpenID Config: '
                        'missing issuer: {}'.format(openid_config_json))
                # Ensure public_issuer_url (only non-empty) matches what came back in
                # the discovery doc.
                if public_issuer_url and (public_issuer_url != issuer_url):
                    raise exceptions.Error(
                        '--public-issuer-url {} did not match issuer '
                        'returned in discovery doc: {}'.format(
                            public_issuer_url, issuer_url))

                # Request the JWKS from the cluster if we need it (either for setting
                # up the GCS bucket or getting public keys for private issuers). In
                # the private issuer case, we set private_keyset_json, which is used
                # later to upload the JWKS in the Hub Membership.
                if self.ReleaseTrack() is base.ReleaseTrack.ALPHA:
                    if args.manage_workload_identity_bucket:
                        api_util.CreateWorkloadIdentityBucket(
                            project, issuer_url, openid_config_json,
                            kube_client.GetOpenIDKeyset())
                    elif args.has_private_issuer:
                        private_keyset_json = kube_client.GetOpenIDKeyset()

            # Attempt to create a membership.
            already_exists = False

            obj = None
            # For backward compatiblity, check if a membership was previously created
            # using the cluster uuid.
            parent = api_util.ParentRef(project, 'global')
            membership_id = uuid
            resource_name = api_util.MembershipRef(project, 'global', uuid)
            obj = self._CheckMembershipWithUUID(resource_name,
                                                args.CLUSTER_NAME)
            if obj:
                # The membership exists and has the same description.
                already_exists = True
            else:
                # Attempt to create a new membership using cluster_name.
                membership_id = args.CLUSTER_NAME
                resource_name = api_util.MembershipRef(project, 'global',
                                                       args.CLUSTER_NAME)
                try:
                    self._VerifyClusterExclusivity(kube_client, parent,
                                                   membership_id)
                    obj = api_util.CreateMembership(
                        project, args.CLUSTER_NAME,
                        args.CLUSTER_NAME, gke_cluster_self_link, uuid,
                        self.ReleaseTrack(), issuer_url, private_keyset_json)
                except apitools_exceptions.HttpConflictError as e:
                    # If the error is not due to the object already existing, re-raise.
                    error = core_api_exceptions.HttpErrorPayload(e)
                    if error.status_description != 'ALREADY_EXISTS':
                        raise
                    obj = api_util.GetMembership(resource_name,
                                                 self.ReleaseTrack())
                    if not obj.externalId:
                        raise exceptions.Error(
                            'invalid membership {0} does not have '
                            'external_id field set. We cannot determine '
                            'if registration is requested against a '
                            'valid existing Membership. Consult the '
                            'documentation on container hub memberships '
                            'update for more information or run gcloud '
                            'container hub memberships delete {0} if you '
                            'are sure that this is an invalid or '
                            'otherwise stale Membership'.format(membership_id))
                    if obj.externalId != uuid:
                        raise exceptions.Error(
                            'membership {0} already exists in the project'
                            ' with another cluster. If this operation is'
                            ' intended, please run `gcloud container '
                            'hub memberships delete {0}` and register '
                            'again.'.format(membership_id))

                    # The membership exists with same cluster_name.
                    already_exists = True

            # In case of an existing membership, check with the user to upgrade the
            # Connect-Agent.
            if already_exists:
                # Update Membership when required. Scenarios that require updates:
                # 1. membership.authority is set, but there is now no issuer URL.
                #    This means the user is disabling Workload Identity.
                # 2. membership.authority is not set, but there is now an
                #    issuer URL. This means the user is enabling Workload Identity.
                # 3. membership.authority is set, but the issuer URL is different
                #    from that set in membership.authority.issuer. This is technically
                #    an error, but we defer to validation in the API.
                # 4. membership.authority.oidcJwks is set, but the private keyset
                #    we got from the cluster differs from the keyset in the membership.
                #    This means the user is updating the public keys, and we should
                #    update to the latest keyset in the membership.
                if (  # scenario 1, disabling WI
                    (obj.authority and not issuer_url) or
                        # scenario 2, enabling WI
                    (issuer_url and not obj.authority) or
                    (obj.authority and
                     # scenario 3, issuer changed
                     ((obj.authority.issuer != issuer_url) or
                      # scenario 4, JWKS changed
                      (private_keyset_json and obj.authority.oidcJwks and
                       (obj.authority.oidcJwks.decode('utf-8') !=
                        private_keyset_json))))):
                    console_io.PromptContinue(
                        message=hub_util.GenerateWIUpdateMsgString(
                            obj, issuer_url, resource_name, args.CLUSTER_NAME),
                        cancel_on_no=True)
                    try:
                        api_util.UpdateMembership(
                            resource_name,
                            obj,
                            'authority',
                            self.ReleaseTrack(),
                            issuer_url=issuer_url,
                            oidc_jwks=private_keyset_json)
                        log.status.Print(
                            'Updated the membership [{}] for the cluster [{}]'.
                            format(resource_name, args.CLUSTER_NAME))
                    except Exception as e:
                        raise exceptions.Error(
                            'Error in updating the membership [{}]:{}'.format(
                                resource_name, e))
                else:
                    console_io.PromptContinue(
                        message=
                        'A membership [{}] for the cluster [{}] already exists. '
                        'Continuing will reinstall the Connect agent deployment to use a '
                        'new image (if one is available).'.format(
                            resource_name, args.CLUSTER_NAME),
                        cancel_on_no=True)
            else:
                log.status.Print(
                    'Created a new membership [{}] for the cluster [{}]'.
                    format(resource_name, args.CLUSTER_NAME))

            # Attempt to update the existing agent deployment, or install a new agent
            # if necessary.
            try:
                self._InstallOrUpdateExclusivityArtifacts(
                    kube_client, resource_name)
                agent_util.DeployConnectAgent(kube_client, args,
                                              service_account_key_data,
                                              docker_credential_data,
                                              resource_name,
                                              self.ReleaseTrack())
            except Exception as e:
                log.status.Print(
                    'Error in installing the Connect Agent: {}'.format(e))
                # In case of a new membership, we need to clean up membership and
                # resources if we failed to install the Connect Agent.
                if not already_exists:
                    api_util.DeleteMembership(resource_name,
                                              self.ReleaseTrack())
                    exclusivity_util.DeleteMembershipResources(kube_client)
                raise
            log.status.Print(
                'Finished registering the cluster [{}] with the Hub.'.format(
                    args.CLUSTER_NAME))
            return obj
    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
        config_changes = [_SetClientNameAndVersion()] + config_changes
        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:
                    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:
            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)
            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))