| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 | from __future__ import absolute_importfrom __future__ import unicode_literalsimport loggingfrom docker.errors import NotFoundfrom docker.utils import version_ltfrom .config import ConfigurationErrorfrom .config.types import VolumeSpecfrom .const import LABEL_PROJECTfrom .const import LABEL_VOLUMElog = logging.getLogger(__name__)class Volume(object):    def __init__(self, client, project, name, driver=None, driver_opts=None,                 external=False, labels=None, custom_name=False):        self.client = client        self.project = project        self.name = name        self.driver = driver        self.driver_opts = driver_opts        self.external = external        self.labels = labels        self.custom_name = custom_name    def create(self):        return self.client.create_volume(            self.full_name, self.driver, self.driver_opts, labels=self._labels        )    def remove(self):        if self.external:            log.info("Volume %s is external, skipping", self.full_name)            return        log.info("Removing volume %s", self.full_name)        return self.client.remove_volume(self.full_name)    def inspect(self):        return self.client.inspect_volume(self.full_name)    def exists(self):        try:            self.inspect()        except NotFound:            return False        return True    @property    def full_name(self):        if self.custom_name:            return self.name        return '{0}_{1}'.format(self.project, self.name)    @property    def _labels(self):        if version_lt(self.client._version, '1.23'):            return None        labels = self.labels.copy() if self.labels else {}        labels.update({            LABEL_PROJECT: self.project,            LABEL_VOLUME: self.name,        })        return labelsclass ProjectVolumes(object):    def __init__(self, volumes):        self.volumes = volumes    @classmethod    def from_config(cls, name, config_data, client):        config_volumes = config_data.volumes or {}        volumes = {            vol_name: Volume(                client=client,                project=name,                name=data.get('name', vol_name),                driver=data.get('driver'),                driver_opts=data.get('driver_opts'),                custom_name=data.get('name') is not None,                labels=data.get('labels'),                external=bool(data.get('external', False))            )            for vol_name, data in config_volumes.items()        }        return cls(volumes)    def remove(self):        for volume in self.volumes.values():            try:                volume.remove()            except NotFound:                log.warn("Volume %s not found.", volume.full_name)    def initialize(self):        try:            for volume in self.volumes.values():                volume_exists = volume.exists()                if volume.external:                    log.debug(                        'Volume {0} declared as external. No new '                        'volume will be created.'.format(volume.name)                    )                    if not volume_exists:                        raise ConfigurationError(                            'Volume {name} declared as external, but could'                            ' not be found. Please create the volume manually'                            ' using `{command}{name}` and try again.'.format(                                name=volume.full_name,                                command='docker volume create --name='                            )                        )                    continue                if not volume_exists:                    log.info(                        'Creating volume "{0}" with {1} driver'.format(                            volume.full_name, volume.driver or 'default'                        )                    )                    volume.create()                else:                    check_remote_volume_config(volume.inspect(), volume)        except NotFound:            raise ConfigurationError(                'Volume %s specifies nonexistent driver %s' % (volume.name, volume.driver)            )    def namespace_spec(self, volume_spec):        if not volume_spec.is_named_volume:            return volume_spec        if isinstance(volume_spec, VolumeSpec):            volume = self.volumes[volume_spec.external]            return volume_spec._replace(external=volume.full_name)        else:            volume_spec.source = self.volumes[volume_spec.source].full_name            return volume_specclass VolumeConfigChangedError(ConfigurationError):    def __init__(self, local, property_name, local_value, remote_value):        super(VolumeConfigChangedError, self).__init__(            'Configuration for volume {vol_name} specifies {property_name} '            '{local_value}, but a volume with the same name uses a different '            '{property_name} ({remote_value}). If you wish to use the new '            'configuration, please remove the existing volume "{full_name}" '            'first:\n$ docker volume rm {full_name}'.format(                vol_name=local.name, property_name=property_name,                local_value=local_value, remote_value=remote_value,                full_name=local.full_name            )        )def check_remote_volume_config(remote, local):    if local.driver and remote.get('Driver') != local.driver:        raise VolumeConfigChangedError(local, 'driver', local.driver, remote.get('Driver'))    local_opts = local.driver_opts or {}    remote_opts = remote.get('Options') or {}    for k in set.union(set(remote_opts.keys()), set(local_opts.keys())):        if k.startswith('com.docker.'):  # These options are set internally            continue        if remote_opts.get(k) != local_opts.get(k):            raise VolumeConfigChangedError(                local, '"{}" driver_opt'.format(k), local_opts.get(k), remote_opts.get(k),            )    local_labels = local.labels or {}    remote_labels = remote.get('Labels') or {}    for k in set.union(set(remote_labels.keys()), set(local_labels.keys())):        if k.startswith('com.docker.'):  # We are only interested in user-specified labels            continue        if remote_labels.get(k) != local_labels.get(k):            log.warn(                'Volume {}: label "{}" has changed. It may need to be'                ' recreated.'.format(local.name, k)            )
 |