475 lines
16 KiB
Python
475 lines
16 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2020 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.
|
|
"""Wraps a resource message with a container with convenience methods."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import unicode_literals
|
|
|
|
import collections
|
|
import json
|
|
from typing import Mapping, Sequence
|
|
|
|
from googlecloudsdk.api_lib.run import k8s_object
|
|
|
|
try:
|
|
# Python 3.3 and above.
|
|
collections_abc = collections.abc
|
|
except AttributeError:
|
|
collections_abc = collections
|
|
|
|
CLOUDSQL_ANNOTATION = k8s_object.RUN_GROUP + '/cloudsql-instances'
|
|
VPC_ACCESS_ANNOTATION = 'run.googleapis.com/vpc-access-connector'
|
|
SANDBOX_ANNOTATION = 'run.googleapis.com/execution-environment'
|
|
CMEK_KEY_ANNOTATION = 'run.googleapis.com/encryption-key'
|
|
POST_CMEK_KEY_REVOCATION_ACTION_TYPE_ANNOTATION = (
|
|
'run.googleapis.com/post-key-revocation-action-type'
|
|
)
|
|
ENCRYPTION_KEY_SHUTDOWN_HOURS_ANNOTATION = (
|
|
'run.googleapis.com/encryption-key-shutdown-hours'
|
|
)
|
|
SECRETS_ANNOTATION = 'run.googleapis.com/secrets'
|
|
CPU_THROTTLE_ANNOTATION = 'run.googleapis.com/cpu-throttling'
|
|
COLD_START_BOOST_ANNOTATION = 'run.googleapis.com/startup-cpu-boost'
|
|
DISABLE_HEALTH_CHECK_ANNOTATION = 'run.googleapis.com/health-check-disabled'
|
|
DISABLE_IAM_ANNOTATION = 'run.googleapis.com/invoker-iam-disabled'
|
|
DISABLE_URL_ANNOTATION = 'run.googleapis.com/default-url-disabled'
|
|
|
|
EGRESS_SETTINGS_ANNOTATION = 'run.googleapis.com/vpc-access-egress'
|
|
EGRESS_SETTINGS_ALL = 'all'
|
|
EGRESS_SETTINGS_ALL_TRAFFIC = 'all-traffic'
|
|
EGRESS_SETTINGS_PRIVATE_RANGES_ONLY = 'private-ranges-only'
|
|
|
|
|
|
class ContainerResource(k8s_object.KubernetesObject):
|
|
"""Wraps a resource message with a container, making fields more convenient.
|
|
|
|
Provides convience fields for Cloud Run resources that contain a container.
|
|
These resources also typically have other overlapping fields such as volumes
|
|
which are also handled by this wrapper.
|
|
"""
|
|
|
|
@property
|
|
def env_vars(self):
|
|
"""Returns a mutable, dict-like object to manage env vars.
|
|
|
|
The returned object can be used like a dictionary, and any modifications to
|
|
the returned object (i.e. setting and deleting keys) modify the underlying
|
|
nested env vars fields.
|
|
"""
|
|
return self.container.env_vars
|
|
|
|
@property
|
|
def image(self):
|
|
"""URL to container."""
|
|
return self.container.image
|
|
|
|
@image.setter
|
|
def image(self, value):
|
|
self.container.image = value
|
|
|
|
@property
|
|
def command(self):
|
|
"""command to be invoked by container."""
|
|
return self.container.command
|
|
|
|
@property
|
|
def container(self):
|
|
"""The container in the revisionTemplate."""
|
|
containers = self.containers.values()
|
|
if not containers:
|
|
return self.containers['']
|
|
|
|
if len(containers) == 1:
|
|
return next(iter(containers))
|
|
|
|
if self.KIND == 'TaskTemplateSpec':
|
|
raise ValueError(
|
|
'the target job has multiple containers, a container name must be'
|
|
' specified via --container flag'
|
|
)
|
|
|
|
for container in containers:
|
|
if container.ports:
|
|
return container
|
|
|
|
raise ValueError('missing ingress container')
|
|
|
|
@property
|
|
def containers(self):
|
|
"""The containers in the revisionTemplate."""
|
|
return ContainersAsDictionaryWrapper(
|
|
self.spec.containers, self.volumes, self._messages
|
|
)
|
|
|
|
@property
|
|
def resource_limits(self):
|
|
"""The resource limits as a dictionary { resource name: limit}."""
|
|
return self.container.resource_limits
|
|
|
|
@property
|
|
def volumes(self):
|
|
"""Returns a dict-like object to manage volumes.
|
|
|
|
There are additional properties on the object (e.g. `.secrets`) that can
|
|
be used to access a mutable, dict-like object for managing volumes of a
|
|
given type. Any modifications to the returned object for these properties
|
|
(i.e. setting and deleting keys) modify the underlying nested volumes.
|
|
"""
|
|
return VolumesAsDictionaryWrapper(self.spec.volumes, self._messages.Volume)
|
|
|
|
@property
|
|
def dependencies(self) -> Mapping[str, Sequence[str]]:
|
|
"""Returns a dictionary of container dependencies.
|
|
|
|
Container dependencies are stored in the
|
|
'run.googleapis.com/container-dependencies' annotation. The returned
|
|
dictionary maps containers to a list of their dependencies by name. Note
|
|
that updates to the returned dictionary do not update the resource's
|
|
container dependencies unless the dependencies setter is used.
|
|
"""
|
|
dependencies = {}
|
|
if k8s_object.CONTAINER_DEPENDENCIES_ANNOTATION in self.annotations:
|
|
dependencies = json.loads(
|
|
self.annotations[k8s_object.CONTAINER_DEPENDENCIES_ANNOTATION]
|
|
)
|
|
return dependencies
|
|
|
|
@dependencies.setter
|
|
def dependencies(self, dependencies: Mapping[str, Sequence[str]]):
|
|
"""Sets the resource's container dependencies.
|
|
|
|
Args:
|
|
dependencies: A dictionary mapping containers to a list of their
|
|
dependencies by name.
|
|
|
|
Container dependencies are stored in the
|
|
'run.googleapis.com/container-dependencies' annotation as json. Setting an
|
|
empty set of dependencies will clear this annotation.
|
|
"""
|
|
if dependencies:
|
|
self.annotations[k8s_object.CONTAINER_DEPENDENCIES_ANNOTATION] = (
|
|
json.dumps({k: list(v) for k, v in dependencies.items()})
|
|
)
|
|
elif k8s_object.CONTAINER_DEPENDENCIES_ANNOTATION in self.annotations:
|
|
del self.annotations[k8s_object.CONTAINER_DEPENDENCIES_ANNOTATION]
|
|
|
|
|
|
class Container(object):
|
|
"""Wraps a container message with dict-like wrappers for env_vars, volume_mounts, and resource_limits.
|
|
|
|
All other properties are delegated to the underlying container message.
|
|
"""
|
|
|
|
def __init__(self, volumes, messages_mod, container=None, **kwargs):
|
|
if not container:
|
|
container = messages_mod.Container(**kwargs)
|
|
object.__setattr__(self, '_volumes', volumes)
|
|
object.__setattr__(self, '_messages', messages_mod)
|
|
object.__setattr__(self, '_m', container)
|
|
|
|
@property
|
|
def env_vars(self):
|
|
"""Returns a mutable, dict-like object to manage env vars.
|
|
|
|
The returned object can be used like a dictionary, and any modifications to
|
|
the returned object (i.e. setting and deleting keys) modify the underlying
|
|
nested env vars fields.
|
|
"""
|
|
return EnvVarsAsDictionaryWrapper(self._m.env, self._messages.EnvVar)
|
|
|
|
@property
|
|
def volume_mounts(self):
|
|
"""Returns a mutable, dict-like object to manage volume mounts.
|
|
|
|
The returned object can be used like a dictionary, and any modifications to
|
|
the returned object (i.e. setting and deleting keys) modify the underlying
|
|
nested volume mounts. There are additional properties on the object
|
|
(e.g. `.secrets` that can be used to access a mutable dict-like object for
|
|
a volume mounts that mount volumes of a given type.
|
|
"""
|
|
return VolumeMountsAsDictionaryWrapper(
|
|
self._volumes, self._m.volumeMounts, self._messages.VolumeMount
|
|
)
|
|
|
|
def _EnsureResources(self):
|
|
limits_cls = self._messages.ResourceRequirements.LimitsValue
|
|
if self.resources is not None:
|
|
if self.resources.limits is None:
|
|
self.resources.limits = k8s_object.InitializedInstance(limits_cls)
|
|
else:
|
|
self.resources = k8s_object.InitializedInstance(
|
|
self._messages.ResourceRequirements
|
|
)
|
|
|
|
@property
|
|
def resource_limits(self):
|
|
"""The resource limits as a dictionary { resource name: limit}."""
|
|
self._EnsureResources()
|
|
return k8s_object.KeyValueListAsDictionaryWrapper(
|
|
self.resources.limits.additionalProperties,
|
|
self._messages.ResourceRequirements.LimitsValue.AdditionalProperty,
|
|
key_field='key',
|
|
value_field='value',
|
|
)
|
|
|
|
def MakeSerializable(self):
|
|
return self._m
|
|
|
|
def __getattr__(self, name):
|
|
return getattr(self._m, name)
|
|
|
|
def __setattr__(self, name, value):
|
|
setattr(self._m, name, value)
|
|
|
|
def MountedVolumeJoin(self, subgroup=None):
|
|
vols = self._volumes
|
|
mounts = self.volume_mounts
|
|
if subgroup:
|
|
vols = getattr(vols, subgroup)
|
|
mounts = getattr(mounts, subgroup)
|
|
return {path: vols.get(vol) for path, vol in mounts.items()}
|
|
|
|
def NamedMountedVolumeJoin(self, subgroup=None):
|
|
vols = self._volumes
|
|
mounts = self.volume_mounts
|
|
if subgroup:
|
|
vols = getattr(vols, subgroup)
|
|
mounts = getattr(mounts, subgroup)
|
|
return {path: (vol, vols.get(vol)) for path, vol in mounts.items()}
|
|
|
|
|
|
class ContainerSequenceWrapper(collections_abc.MutableSequence):
|
|
"""Wraps a list of containers wrapping each element with the Container wrapper class."""
|
|
|
|
def __init__(self, containers_to_wrap, volumes, messages_mod):
|
|
super(ContainerSequenceWrapper, self).__init__()
|
|
self._containers = containers_to_wrap
|
|
self._volumes = volumes
|
|
self._messages = messages_mod
|
|
|
|
def __getitem__(self, index):
|
|
return Container(self._volumes, self._messages, self._containers[index])
|
|
|
|
def __len__(self):
|
|
return len(self._containers)
|
|
|
|
def __setitem__(self, index, container):
|
|
self._containers[index] = container.MakeSerializable()
|
|
|
|
def __delitem__(self, index):
|
|
del self._containers[index]
|
|
|
|
def insert(self, index, value):
|
|
self._containers.insert(index, value.MakeSerializable())
|
|
|
|
def MakeSerializable(self):
|
|
return self._containers
|
|
|
|
|
|
class ContainersAsDictionaryWrapper(k8s_object.ListAsDictionaryWrapper):
|
|
"""Wraps a list of containers in a mutable dict-like object mapping containers by name.
|
|
|
|
Accessing a container name that does not exist will automatically add a new
|
|
container with the specified name to the underlying list.
|
|
"""
|
|
|
|
def __init__(self, containers_to_wrap, volumes, messages_mod):
|
|
"""Wraps a list of containers in a mutable dict-like object.
|
|
|
|
Args:
|
|
containers_to_wrap: list[Container], list of containers to treat as a
|
|
dict.
|
|
volumes: the volumes defined in the containing resource used to classify
|
|
volume mounts
|
|
messages_mod: the messages module
|
|
"""
|
|
self._volumes = volumes
|
|
self._messages = messages_mod
|
|
super(ContainersAsDictionaryWrapper, self).__init__(
|
|
ContainerSequenceWrapper(containers_to_wrap, volumes, messages_mod)
|
|
)
|
|
|
|
def __getitem__(self, key):
|
|
try:
|
|
return super(ContainersAsDictionaryWrapper, self).__getitem__(key)
|
|
except KeyError:
|
|
container = Container(self._volumes, self._messages, name=key)
|
|
self._m.append(container)
|
|
return container
|
|
|
|
def MakeSerializable(self):
|
|
return (
|
|
super(ContainersAsDictionaryWrapper, self)
|
|
.MakeSerializable() # ContainerSequenceWrapper
|
|
.MakeSerializable()
|
|
)
|
|
|
|
|
|
class EnvVarsAsDictionaryWrapper(k8s_object.ListAsDictionaryWrapper):
|
|
"""Wraps a list of env vars in a dict-like object.
|
|
|
|
Additionally provides properties to access env vars of specific type in a
|
|
mutable dict-like object.
|
|
"""
|
|
|
|
def __init__(self, env_vars_to_wrap, env_var_class):
|
|
"""Wraps a list of env vars in a dict-like object.
|
|
|
|
Args:
|
|
env_vars_to_wrap: list[EnvVar], list of env vars to treat as a dict.
|
|
env_var_class: type of the underlying EnvVar objects.
|
|
"""
|
|
super(EnvVarsAsDictionaryWrapper, self).__init__(env_vars_to_wrap)
|
|
self._env_vars = env_vars_to_wrap
|
|
self._env_var_class = env_var_class
|
|
|
|
@property
|
|
def literals(self):
|
|
"""Mutable dict-like object for env vars with a string literal.
|
|
|
|
Note that if neither value nor valueFrom is specified, the list entry will
|
|
be treated as a literal empty string.
|
|
|
|
Returns:
|
|
A mutable, dict-like object for managing string literal env vars.
|
|
"""
|
|
return k8s_object.KeyValueListAsDictionaryWrapper(
|
|
self._env_vars,
|
|
self._env_var_class,
|
|
filter_func=lambda env_var: env_var.valueFrom is None,
|
|
)
|
|
|
|
@property
|
|
def secrets(self):
|
|
"""Mutable dict-like object for vars with a secret source type."""
|
|
|
|
def _FilterSecretEnvVars(env_var):
|
|
return (
|
|
env_var.valueFrom is not None
|
|
and env_var.valueFrom.secretKeyRef is not None
|
|
)
|
|
|
|
return k8s_object.KeyValueListAsDictionaryWrapper(
|
|
self._env_vars,
|
|
self._env_var_class,
|
|
value_field='valueFrom',
|
|
filter_func=_FilterSecretEnvVars,
|
|
)
|
|
|
|
@property
|
|
def config_maps(self):
|
|
"""Mutable dict-like object for vars with a config map source type."""
|
|
|
|
def _FilterConfigMapEnvVars(env_var):
|
|
return (
|
|
env_var.valueFrom is not None
|
|
and env_var.valueFrom.configMapKeyRef is not None
|
|
)
|
|
|
|
return k8s_object.KeyValueListAsDictionaryWrapper(
|
|
self._env_vars,
|
|
self._env_var_class,
|
|
value_field='valueFrom',
|
|
filter_func=_FilterConfigMapEnvVars,
|
|
)
|
|
|
|
|
|
class VolumesAsDictionaryWrapper(k8s_object.ListAsDictionaryWrapper):
|
|
"""Wraps a list of volumes in a dict-like object.
|
|
|
|
Additionally provides properties to access volumes of specific type in a
|
|
mutable dict-like object.
|
|
"""
|
|
|
|
def __init__(self, volumes_to_wrap, volume_class):
|
|
"""Wraps a list of volumes in a dict-like object.
|
|
|
|
Args:
|
|
volumes_to_wrap: list[Volume], list of volumes to treat as a dict.
|
|
volume_class: type of the underlying Volume objects.
|
|
"""
|
|
super(VolumesAsDictionaryWrapper, self).__init__(volumes_to_wrap)
|
|
self._volumes = volumes_to_wrap
|
|
self._volume_class = volume_class
|
|
|
|
@property
|
|
def secrets(self):
|
|
"""Mutable dict-like object for volumes with a secret source type."""
|
|
return k8s_object.KeyValueListAsDictionaryWrapper(
|
|
self._volumes,
|
|
self._volume_class,
|
|
value_field='secret',
|
|
filter_func=lambda volume: volume.secret is not None,
|
|
)
|
|
|
|
@property
|
|
def config_maps(self):
|
|
"""Mutable dict-like object for volumes with a config map source type."""
|
|
return k8s_object.KeyValueListAsDictionaryWrapper(
|
|
self._volumes,
|
|
self._volume_class,
|
|
value_field='configMap',
|
|
filter_func=lambda volume: volume.configMap is not None,
|
|
)
|
|
|
|
|
|
class VolumeMountsAsDictionaryWrapper(
|
|
k8s_object.KeyValueListAsDictionaryWrapper
|
|
):
|
|
"""Wraps a list of volume mounts in a mutable dict-like object.
|
|
|
|
Additionally provides properties to access mounts that are mounting volumes
|
|
of specific type in a mutable dict-like object.
|
|
"""
|
|
|
|
def __init__(self, volumes, mounts_to_wrap, mount_class):
|
|
"""Wraps a list of volume mounts in a mutable dict-like object.
|
|
|
|
Args:
|
|
volumes: associated VolumesAsDictionaryWrapper obj
|
|
mounts_to_wrap: list[VolumeMount], list of mounts to treat as a dict.
|
|
mount_class: type of the underlying VolumeMount objects.
|
|
"""
|
|
super(VolumeMountsAsDictionaryWrapper, self).__init__(
|
|
mounts_to_wrap,
|
|
mount_class,
|
|
key_field='mountPath',
|
|
value_field='name',
|
|
)
|
|
self._volumes = volumes
|
|
|
|
@property
|
|
def secrets(self):
|
|
"""Mutable dict-like object for mounts whose volumes have a secret source type."""
|
|
return k8s_object.KeyValueListAsDictionaryWrapper(
|
|
self._m,
|
|
self._item_class,
|
|
key_field=self._key_field,
|
|
value_field=self._value_field,
|
|
filter_func=lambda mount: mount.name in self._volumes.secrets,
|
|
)
|
|
|
|
@property
|
|
def config_maps(self):
|
|
"""Mutable dict-like object for mounts whose volumes have a config map source type."""
|
|
return k8s_object.KeyValueListAsDictionaryWrapper(
|
|
self._m,
|
|
self._item_class,
|
|
key_field=self._key_field,
|
|
value_field=self._value_field,
|
|
filter_func=lambda mount: mount.name in self._volumes.config_maps,
|
|
)
|