File: //lib/google-cloud-sdk/lib/googlecloudsdk/api_lib/kuberun/revision.py
# -*- 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.
"""Wrapper for JSON-based Kubernetes object's metadata."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from googlecloudsdk.api_lib.kuberun import kubernetesobject
from googlecloudsdk.api_lib.kuberun import mapobject
LAST_MODIFIER_ANNOTATION = 'serving.knative.dev/lastModifier'
MIN_SCALE_ANNOTATION = 'autoscaling.knative.dev/minScale'
MAX_SCALE_ANNOTATION = 'autoscaling.knative.dev/maxScale'
SERVICE_LABEL = 'serving.knative.dev/service'
USER_IMAGE_ANNOTATION = kubernetesobject.CLIENT_GROUP + '/user-image'
class Revision(kubernetesobject.KubernetesObject):
"""Wraps the revision field of Knative service."""
def Kind(self):
return 'Revision'
@property
def labels(self):
return self.metadata.labels
@property
def resource_limits(self):
return self.container.resources.get('limits', {})
@property
def creation_timestamp(self):
return self.metadata.creationTimestamp
@property
def last_modifier(self):
return self.metadata.annotations.get(LAST_MODIFIER_ANNOTATION)
@property
def service_name(self):
return self.labels[SERVICE_LABEL]
@property
def spec(self):
return Spec(self._props['spec'])
@property
def container(self):
return Container(self.spec.container)
@property
def image(self):
return self.container.image
@property
def env_vars(self):
return EnvVars(self.container.env)
@property
def concurrency(self):
return self.spec.concurrency
@property
def timeout(self):
return self.spec.timeout
# copied from run/revision
def UserImage(self, service_user_image=None):
"""Human-readable "what's deployed".
Sometimes references a client.knative.dev/user-image annotation on the
revision or service to determine what the user intended to deploy. In that
case, we can display that, and show the user the hash prefix as a note that
it's at that specific hash.
TODO (b/168630076): Investigate if this code can be shared with the Run
binary.
Arguments:
service_user_image: Optional[str], the contents of the user image
annotation on the service.
Returns:
a string representing the user deployment intent.
"""
if not self.image:
return None
if '@' not in self.image:
return self.image
user_image = (
self.annotations.get(USER_IMAGE_ANNOTATION) or service_user_image)
if not user_image:
return self.image
# The image should be in the format base@sha256:hashhashhash
base, h = self.image.split('@')
if ':' in h:
_, h = h.split(':')
if not user_image.startswith(base):
# The user-image is out of date.
return self.image
if len(h) > 8:
h = h[:8] + '...'
return user_image + ' at ' + h
@property
def volumes(self):
return Volumes(self.spec.volumes)
@property
def volume_mounts(self):
if self.container:
return VolumeMounts(self.volumes, self.container.volume_mounts)
else:
return []
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()}
@property
def active(self):
active_cond = [x for x in self.status.conditions if x.type == 'Active']
if active_cond:
return active_cond[0].status
return None
def ReadySymbolAndColor(self):
if not self.ready:
return '!', 'yellow'
return super(Revision, self).ReadySymbolAndColor()
class Spec(mapobject.MapObject):
"""Wraps the spec field of the Template resource."""
@property
def container(self):
return self._props['containers'][0]
@property
def concurrency(self):
return self.containerConcurrency
@property
def timeout(self):
return self.timeoutSeconds
@property
def volumes(self):
if 'volumes' in self._props:
return self._props['volumes']
else:
return []
class Container(mapobject.MapObject):
"""Wraps a container resource."""
@property
def command(self):
return self._props.get('command', [])
@property
def args(self):
return self._props.get('args', [])
@property
def ports(self):
return [ContainerPort(x) for x in self._props.get('ports', [])]
@property
def volume_mounts(self):
if 'volumeMounts' in self._props:
return self._props['volumeMounts']
else:
return []
class EnvVars():
"""Represents the list of env vars/secrets/config maps.
Provides properties to access the various type of env vars.
"""
def __init__(self, env_var_list):
if env_var_list:
self._env_var_list = env_var_list
else:
self._env_var_list = dict()
@property
def literals(self):
return {
env['name']: env.get('value')
for env in self._env_var_list
if env.get('valueFrom') is None
}
@property
def secrets(self):
return {
env['name']: EnvValueFrom(env.get('valueFrom'))
for env in self._env_var_list
if env.get('valueFrom') and env.get('valueFrom').get('secretKeyRef')
}
@property
def config_maps(self):
return {
env['name']: EnvValueFrom(env.get('valueFrom'))
for env in self._env_var_list
if env.get('valueFrom') and env.get('valueFrom').get('configMapKeyRef')
}
class EnvValueFrom(mapobject.MapObject):
"""Represents the ValueFrom field of an EnvVar."""
@property
def secretKeyRef(self):
if self._props.get('secretKeyRef'):
return SecretKey(self._props.get('secretKeyRef'))
else:
return None
@property
def configMapKeyRef(self):
if self._props.get('configMapKeyRef'):
return ConfigMapKey(self._props.get('configMapKeyRef'))
else:
return None
class SecretKey(mapobject.MapObject):
pass
class ConfigMapKey(mapobject.MapObject):
pass
class ContainerPort(mapobject.MapObject):
pass
class Volumes():
"""Represents the volumes in a revision.spec."""
def __init__(self, volumes):
self._volumes = volumes
@property
def secrets(self):
return {
vol['name']: VolumeItem(vol.get('secret'))
for vol in self._volumes
if vol.get('secret')
}
@property
def config_maps(self):
return {
vol['name']: VolumeItem(vol.get('configMap'))
for vol in self._volumes
if vol.get('configMap')
}
class VolumeMounts():
"""Represents the volume mounts in a revision.spec.container."""
def __init__(self, volumes, volume_mounts):
self._volumes = volumes
self._volume_mounts = volume_mounts
@property
def secrets(self):
return {
mount['mountPath']: mount['name']
for mount in self._volume_mounts
if mount['name'] in self._volumes.secrets
}
@property
def config_maps(self):
return {
mount['mountPath']: mount['name']
for mount in self._volume_mounts
if mount['name'] in self._volumes.config_maps
}
class VolumeItem(mapobject.MapObject):
@property
def items(self):
return [KeyToPath(x) for x in self._props.get('items', [])]
class KeyToPath(mapobject.MapObject):
pass