HEX
Server: Apache/2.4.65 (Ubuntu)
System: Linux ielts-store-v2 6.8.0-1036-gcp #38~22.04.1-Ubuntu SMP Thu Aug 14 01:19:18 UTC 2025 x86_64
User: root (0)
PHP: 7.2.34-54+ubuntu20.04.1+deb.sury.org+1
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,
Upload Files
File: //snap/google-cloud-cli/current/lib/googlecloudsdk/command_lib/code/cloud/cloud.py
# -*- coding: utf-8 -*- #
# Copyright 2022 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Library for configuring cloud-based development."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import copy
import os

from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.api_lib.util import messages as messages_util
from googlecloudsdk.command_lib.artifacts import docker_util
from googlecloudsdk.command_lib.code import builders
from googlecloudsdk.command_lib.code import common
from googlecloudsdk.command_lib.code import dataobject
from googlecloudsdk.command_lib.code import yaml_helper
from googlecloudsdk.command_lib.run import exceptions
from googlecloudsdk.command_lib.run import flags as run_flags
from googlecloudsdk.core import exceptions as core_exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core import yaml
from googlecloudsdk.core.util import files

RUN_MESSAGES_MODULE = apis.GetMessagesModule('run', 'v1')

_DEFAULT_BUILDPACK_BUILDER = 'gcr.io/buildpacks/builder'


class ImageFormatError(core_exceptions.Error):
  """An error thrown when the provided image has a tag or hash."""

  def __init__(self, image, fmt):
    super(ImageFormatError, self).__init__(
        message=(
            'Image {} has a {} included. To use locally built image, do '
            'not include digest or tag'
        ).format(image, fmt)
    )


def _IsGcpBaseBuilder(bldr):
  """Return true if the builder is the GCP base builder.

  Args:
    bldr: Name of the builder.

  Returns:
    True if the builder is the GCP base builder.
  """
  return bldr == _DEFAULT_BUILDPACK_BUILDER


def _BuilderFromArg(builder_arg):
  is_gcp_base_builder = _IsGcpBaseBuilder(builder_arg)
  return builders.BuildpackBuilder(
      builder=builder_arg, trust=is_gcp_base_builder, devmode=False
  )


class Settings(dataobject.DataObject):
  """Settings for a Cloud dev deployment.

  Attributes:
    image: image to deploy from local sources
    project: the gcp project to deploy to
    region: the Cloud Run region to deploy to
    service_name: the name of the Cloud Run service to deploy
    builder: the build configuration. Docker and Buildpacks are supported.
    context: the folder in which the build will be executed
    service: the base service to build off of. Using this allows any field not
      explicitly supported by code dev --cloud to still propagate
    cpu: the amount of CPU to be used
    memory: the amount of memory to be specified.
    ar_repo: the Artifact Registry Docker repo to deploy to.
    local_port: the local port to forward the request for.
    service_account: the service identity to use for the deployed service.
  """

  NAMES = [
      'image',
      'project',
      'region',
      'builder',
      'service_name',
      'service',
      'context',
      'cpu',
      'memory',
      'ar_repo',
      'local_port',
      'service_account',
  ]

  @classmethod
  def Defaults(cls):
    dir_name = os.path.basename(files.GetCWD())
    # Service names may not include space, _ and upper case characters.
    service_name = dir_name.replace('_', '-').replace(' ', '-').lower()
    service = RUN_MESSAGES_MODULE.Service(
        apiVersion='serving.knative.dev/v1', kind='Service'
    )
    dockerfile_arg_default = 'Dockerfile'
    bldr = builders.DockerfileBuilder(dockerfile=dockerfile_arg_default)
    return cls(
        service_name=service_name,
        service=service,
        builder=bldr,
        context=os.path.abspath(files.GetCWD()),
    )

  def WithServiceYaml(self, yaml_path):
    """Use a pre-written service yaml for deployment."""
    # TODO(b/256683239): this is partially
    # copied from surface/run/services/replace.py and
    # should be moved somewhere common to avoid duplication

    service_dict = yaml.load_path(yaml_path)
    # 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.
    metadata = yaml_helper.GetOrCreate(service_dict, ['metadata'])
    namespace = metadata.get('namespace', None)
    if namespace is not None and not isinstance(namespace, str):
      service_dict['metadata']['namespace'] = str(namespace)

    try:
      service = messages_util.DictToMessageWithErrorCheck(
          service_dict, RUN_MESSAGES_MODULE.Service
      )
    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/service-resources/service-v1/'
              '#Service.'
          ),
      )
    if self.project:
      service.metadata.namespace = str(self.project)
    replacements = {'service': service}
    # assume first image is the one we're replacing.
    container = service.spec.template.spec.containers[0]
    replacements['image'] = container.image
    if container.resources and container.resources.limits:
      for limit in container.resources.limits.additionalProperties:
        replacements[limit.key] = limit.value
    if service.metadata.name:
      replacements['service_name'] = service.metadata.name
    return self.replace(**replacements)

  def WithArgs(self, args):
    """Update parameters based on arguments."""
    project = properties.VALUES.core.project.Get()
    region = run_flags.GetRegion(args, prompt=True)
    replacements = {'project': project, 'region': region}

    for override_arg in [
        'local_port',
        'memory',
        'cpu',
        'image',
        'service_name',
        'service_account',
    ]:
      if args.IsKnownAndSpecified(override_arg):
        replacements[override_arg] = getattr(args, override_arg)

    context = self.context
    if args.source:
      context = os.path.abspath(args.source)
    replacements['context'] = context

    if args.IsKnownAndSpecified('builder'):
      replacements['builder'] = _BuilderFromArg(args.builder)
    elif args.IsKnownAndSpecified('dockerfile'):
      replacements['builder'] = builders.DockerfileBuilder(
          dockerfile=args.dockerfile
      )
    else:
      if isinstance(self.builder, builders.DockerfileBuilder):
        try:
          replacements['builder'] = self.builder
          replacements['builder'].Validate(context)
        except builders.InvalidLocationError:
          log.status.Print(
              'No Dockerfile detected. '
              'Using GCP buildpacks to build the container'
          )
          replacements['builder'] = _BuilderFromArg(_DEFAULT_BUILDPACK_BUILDER)
    return self.replace(**replacements)

  def Build(self):
    replacements = {}

    if not self.image:
      ar_repo = docker_util.DockerRepo(
          project_id=self.project,
          location_id=self.region,
          repo_id='cloud-run-source-deploy',
      )
      replacements['ar_repo'] = ar_repo
      replacements['image'] = _DefaultImageName(ar_repo, self.service_name)
    return self.replace(**replacements)


def AssembleSettings(args):
  settings = Settings.Defaults()
  context_dir = getattr(args, 'source', None) or os.path.curdir
  service_config = getattr(args, 'service_config', None)
  yaml_file = common.ChooseExistingServiceYaml(context_dir, service_config)
  if yaml_file:
    settings = settings.WithServiceYaml(yaml_file)
  settings = settings.WithArgs(args)
  return settings.Build()


def GenerateService(settings):
  """Generate a service configuration from a Cloud Settings configuration."""
  service = copy.deepcopy(settings.service)
  metadata = service.metadata or RUN_MESSAGES_MODULE.ObjectMeta()
  metadata.name = settings.service_name
  metadata.namespace = str(settings.project)
  service.metadata = metadata
  _BuildSpecTemplate(service)
  if settings.service_account:
    service.spec.template.spec.serviceAccountName = settings.service_account
  container = service.spec.template.spec.containers[0]
  container.image = settings.image
  _FillContainerRequirements(container, settings)
  return service


def _BuildSpecTemplate(service):
  if not service.spec:
    service.spec = RUN_MESSAGES_MODULE.ServiceSpec()
  if not service.spec.template:
    service.spec.template = RUN_MESSAGES_MODULE.RevisionTemplate()
  if not service.spec.template.spec:
    service.spec.template.spec = RUN_MESSAGES_MODULE.RevisionSpec()
  if not service.spec.template.spec.containers:
    service.spec.template.spec.containers = [RUN_MESSAGES_MODULE.Container()]


def _DefaultImageName(ar_repo, service_name):
  return '{repo}/{service}'.format(
      repo=ar_repo.GetDockerString(), service=service_name
  )


def _FillContainerRequirements(container, settings):
  """Set the container CPU and memory limits based on settings."""
  found = set()
  resources = container.resources or RUN_MESSAGES_MODULE.ResourceRequirements()
  limits = (
      resources.limits or RUN_MESSAGES_MODULE.ResourceRequirements.LimitsValue()
  )
  for limit in limits.additionalProperties:
    if limit.key == 'cpu' and settings.cpu:
      limit.value = settings.cpu
    elif limit.key == 'memory' and settings.memory:
      limit.value = settings.memory
    found.add(limit.key)

  # if requirements weren't already specified add them
  if 'cpu' not in found and settings.cpu:
    cpu = (
        RUN_MESSAGES_MODULE.ResourceRequirements.LimitsValue.AdditionalProperty(
            key='cpu', value=str(settings.cpu)
        )
    )
    limits.additionalProperties.append(cpu)
  if 'memory' not in found and settings.memory:
    mem = (
        RUN_MESSAGES_MODULE.ResourceRequirements.LimitsValue.AdditionalProperty(
            key='memory', value=str(settings.memory)
        )
    )
    limits.additionalProperties.append(mem)
  resources.limits = limits
  container.resources = resources


def ValidateSettings(settings):
  if '@' in settings.image:
    raise ImageFormatError(settings.image, 'digest')
  elif ':' in settings.image:
    raise ImageFormatError(settings.image, 'tag')