6540f9319a
This patch fixes 3 issues: - "RuntimeError: dictionary changed size during iteration" - "TypeError: 'map' object is not subscriptable" - "TypeError: object of type 'map' has no len()" Change-Id: If52ab336512f37b6e5ad6c748bef7996c67cb71a
785 lines
28 KiB
Python
785 lines
28 KiB
Python
# Copyright (c) 2015 Mirantis, Inc.
|
|
#
|
|
# 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.
|
|
|
|
import copy
|
|
import operator
|
|
import uuid
|
|
|
|
from enum import Enum
|
|
from oslo_db import exception as db_exc
|
|
import sqlalchemy
|
|
from sqlalchemy import and_
|
|
from sqlalchemy import case
|
|
from sqlalchemy import or_
|
|
import sqlalchemy.orm as orm
|
|
from sqlalchemy.orm import joinedload
|
|
|
|
from glance.common import exception
|
|
from glance.common import semver_db
|
|
from glance.common import timeutils
|
|
from glance.db.sqlalchemy import models_glare as models
|
|
import glance.glare as ga
|
|
from glance.i18n import _LE, _LW
|
|
from oslo_log import log as os_logging
|
|
|
|
LOG = os_logging.getLogger(__name__)
|
|
|
|
|
|
class Visibility(Enum):
|
|
PRIVATE = 'private'
|
|
PUBLIC = 'public'
|
|
SHARED = 'shared'
|
|
|
|
|
|
class State(Enum):
|
|
CREATING = 'creating'
|
|
ACTIVE = 'active'
|
|
DEACTIVATED = 'deactivated'
|
|
DELETED = 'deleted'
|
|
|
|
|
|
TRANSITIONS = {
|
|
State.CREATING: [State.ACTIVE, State.DELETED],
|
|
State.ACTIVE: [State.DEACTIVATED, State.DELETED],
|
|
State.DEACTIVATED: [State.ACTIVE, State.DELETED],
|
|
State.DELETED: []
|
|
}
|
|
|
|
|
|
def create(context, values, session, type_name, type_version=None):
|
|
return _out(_create_or_update(context, values, None, session,
|
|
type_name, type_version))
|
|
|
|
|
|
def update(context, values, artifact_id, session,
|
|
type_name, type_version=None):
|
|
return _out(_create_or_update(context, values, artifact_id, session,
|
|
type_name, type_version))
|
|
|
|
|
|
def delete(context, artifact_id, session, type_name, type_version=None):
|
|
values = {'state': 'deleted'}
|
|
return _out(_create_or_update(context, values, artifact_id, session,
|
|
type_name, type_version))
|
|
|
|
|
|
def _create_or_update(context, values, artifact_id, session, type_name,
|
|
type_version=None):
|
|
values = copy.deepcopy(values)
|
|
with session.begin():
|
|
_set_version_fields(values)
|
|
_validate_values(values)
|
|
_drop_protected_attrs(models.Artifact, values)
|
|
if artifact_id:
|
|
# update existing artifact
|
|
state = values.get('state')
|
|
show_level = ga.Showlevel.BASIC
|
|
if state is not None:
|
|
if state == 'active':
|
|
show_level = ga.Showlevel.DIRECT
|
|
values['published_at'] = timeutils.utcnow()
|
|
if state == 'deleted':
|
|
values['deleted_at'] = timeutils.utcnow()
|
|
|
|
artifact = _get(context, artifact_id, session, type_name,
|
|
type_version, show_level=show_level)
|
|
_validate_transition(artifact.state,
|
|
values.get('state') or artifact.state)
|
|
else:
|
|
# create new artifact
|
|
artifact = models.Artifact()
|
|
if 'id' not in values:
|
|
artifact.id = str(uuid.uuid4())
|
|
else:
|
|
artifact.id = values['id']
|
|
|
|
if 'tags' in values:
|
|
tags = values.pop('tags')
|
|
artifact.tags = _do_tags(artifact, tags)
|
|
|
|
if 'properties' in values:
|
|
properties = values.pop('properties', {})
|
|
artifact.properties = _do_properties(artifact, properties)
|
|
|
|
if 'blobs' in values:
|
|
blobs = values.pop('blobs')
|
|
artifact.blobs = _do_blobs(artifact, blobs)
|
|
|
|
if 'dependencies' in values:
|
|
dependencies = values.pop('dependencies')
|
|
_do_dependencies(artifact, dependencies, session)
|
|
|
|
if values.get('state', None) == 'publish':
|
|
artifact.dependencies.extend(
|
|
_do_transitive_dependencies(artifact, session))
|
|
|
|
artifact.update(values)
|
|
try:
|
|
artifact.save(session=session)
|
|
except db_exc.DBDuplicateEntry:
|
|
LOG.warn(_LW("Artifact with the specified type, name and version "
|
|
"already exists"))
|
|
raise exception.ArtifactDuplicateNameTypeVersion()
|
|
|
|
return artifact
|
|
|
|
|
|
def get(context, artifact_id, session, type_name=None, type_version=None,
|
|
show_level=ga.Showlevel.BASIC):
|
|
artifact = _get(context, artifact_id, session, type_name, type_version,
|
|
show_level)
|
|
return _out(artifact, show_level)
|
|
|
|
|
|
def publish(context, artifact_id, session, type_name,
|
|
type_version=None):
|
|
"""
|
|
Because transitive dependencies are not initially created it has to be done
|
|
manually by calling this function.
|
|
It creates transitive dependencies for the given artifact_id and saves
|
|
them in DB.
|
|
:returns: artifact dict with Transitive show level
|
|
"""
|
|
values = {'state': 'active'}
|
|
return _out(_create_or_update(context, values, artifact_id, session,
|
|
type_name, type_version))
|
|
|
|
|
|
def _validate_transition(source_state, target_state):
|
|
if target_state == source_state:
|
|
return
|
|
try:
|
|
source_state = State(source_state)
|
|
target_state = State(target_state)
|
|
except ValueError:
|
|
raise exception.InvalidArtifactStateTransition(source=source_state,
|
|
target=target_state)
|
|
if (source_state not in TRANSITIONS or
|
|
target_state not in TRANSITIONS[source_state]):
|
|
raise exception.InvalidArtifactStateTransition(source=source_state,
|
|
target=target_state)
|
|
|
|
|
|
def _out(artifact, show_level=ga.Showlevel.BASIC, show_text_properties=True):
|
|
"""
|
|
Transforms sqlalchemy object into dict depending on the show level.
|
|
|
|
:param artifact: sql
|
|
:param show_level: constant from Showlevel class
|
|
:param show_text_properties: for performance optimization it's possible
|
|
to disable loading of massive text properties
|
|
:returns: generated dict
|
|
"""
|
|
res = artifact.to_dict(show_level=show_level,
|
|
show_text_properties=show_text_properties)
|
|
|
|
if show_level >= ga.Showlevel.DIRECT:
|
|
dependencies = artifact.dependencies
|
|
dependencies.sort(key=lambda elem: (elem.artifact_origin,
|
|
elem.name, elem.position))
|
|
res['dependencies'] = {}
|
|
if show_level == ga.Showlevel.DIRECT:
|
|
new_show_level = ga.Showlevel.BASIC
|
|
else:
|
|
new_show_level = ga.Showlevel.TRANSITIVE
|
|
for dep in dependencies:
|
|
if dep.artifact_origin == artifact.id:
|
|
# make array
|
|
for p in res['dependencies'].keys():
|
|
if p == dep.name:
|
|
# add value to array
|
|
res['dependencies'][p].append(
|
|
_out(dep.dest, new_show_level))
|
|
break
|
|
else:
|
|
# create new array
|
|
deparr = [_out(dep.dest, new_show_level)]
|
|
res['dependencies'][dep.name] = deparr
|
|
return res
|
|
|
|
|
|
def _get(context, artifact_id, session, type_name=None, type_version=None,
|
|
show_level=ga.Showlevel.BASIC):
|
|
values = dict(id=artifact_id)
|
|
if type_name is not None:
|
|
values['type_name'] = type_name
|
|
if type_version is not None:
|
|
values['type_version'] = type_version
|
|
_set_version_fields(values)
|
|
try:
|
|
if show_level == ga.Showlevel.NONE:
|
|
query = (
|
|
session.query(models.Artifact).
|
|
options(joinedload(models.Artifact.tags)).
|
|
filter_by(**values))
|
|
else:
|
|
query = (
|
|
session.query(models.Artifact).
|
|
options(joinedload(models.Artifact.properties)).
|
|
options(joinedload(models.Artifact.tags)).
|
|
options(joinedload(models.Artifact.blobs).
|
|
joinedload(models.ArtifactBlob.locations)).
|
|
filter_by(**values))
|
|
|
|
artifact = query.one()
|
|
except orm.exc.NoResultFound:
|
|
LOG.warn(_LW("Artifact with id=%s not found") % artifact_id)
|
|
raise exception.ArtifactNotFound(id=artifact_id)
|
|
if not _check_visibility(context, artifact):
|
|
LOG.warn(_LW("Artifact with id=%s is not accessible") % artifact_id)
|
|
raise exception.ArtifactForbidden(id=artifact_id)
|
|
return artifact
|
|
|
|
|
|
def get_all(context, session, marker=None, limit=None,
|
|
sort_keys=None, sort_dirs=None, filters=None,
|
|
show_level=ga.Showlevel.NONE):
|
|
"""List all visible artifacts"""
|
|
|
|
filters = filters or {}
|
|
|
|
artifacts = _get_all(
|
|
context, session, filters, marker,
|
|
limit, sort_keys, sort_dirs, show_level)
|
|
|
|
return [_out(ns, show_level, show_text_properties=False)
|
|
for ns in artifacts]
|
|
|
|
|
|
def _get_all(context, session, filters=None, marker=None,
|
|
limit=None, sort_keys=None, sort_dirs=None,
|
|
show_level=ga.Showlevel.NONE):
|
|
"""Get all namespaces that match zero or more filters.
|
|
|
|
:param filters: dict of filter keys and values.
|
|
:param marker: namespace id after which to start page
|
|
:param limit: maximum number of namespaces to return
|
|
:param sort_keys: namespace attributes by which results should be sorted
|
|
:param sort_dirs: directions in which results should be sorted (asc, desc)
|
|
"""
|
|
|
|
filters = filters or {}
|
|
|
|
query = _do_artifacts_query(context, session, show_level)
|
|
basic_conds, tag_conds, prop_conds = _do_query_filters(filters)
|
|
|
|
if basic_conds:
|
|
for basic_condition in basic_conds:
|
|
query = query.filter(and_(*basic_condition))
|
|
|
|
if tag_conds:
|
|
for tag_condition in tag_conds:
|
|
query = query.join(models.ArtifactTag, aliased=True).filter(
|
|
and_(*tag_condition))
|
|
|
|
if prop_conds:
|
|
for prop_condition in prop_conds:
|
|
query = query.join(models.ArtifactProperty, aliased=True).filter(
|
|
and_(*prop_condition))
|
|
|
|
marker_artifact = None
|
|
if marker is not None:
|
|
marker_artifact = _get(context, marker, session, None, None)
|
|
|
|
if sort_keys is None:
|
|
sort_keys = [('created_at', None), ('id', None)]
|
|
sort_dirs = ['desc', 'desc']
|
|
else:
|
|
for key in [('created_at', None), ('id', None)]:
|
|
if key not in sort_keys:
|
|
sort_keys.append(key)
|
|
sort_dirs.append('desc')
|
|
|
|
# Note(mfedosin): Workaround to deal with situation that sqlalchemy cannot
|
|
# work with composite keys correctly
|
|
if ('version', None) in sort_keys:
|
|
i = sort_keys.index(('version', None))
|
|
version_sort_dir = sort_dirs[i]
|
|
sort_keys[i:i + 1] = [('version_prefix', None),
|
|
('version_suffix', None),
|
|
('version_meta', None)]
|
|
sort_dirs[i:i + 1] = [version_sort_dir] * 3
|
|
|
|
query = _do_paginate_query(query=query,
|
|
limit=limit,
|
|
sort_keys=sort_keys,
|
|
marker=marker_artifact,
|
|
sort_dirs=sort_dirs)
|
|
|
|
return query.all()
|
|
|
|
|
|
def _do_paginate_query(query, sort_keys=None, sort_dirs=None,
|
|
marker=None, limit=None):
|
|
# Default the sort direction to ascending
|
|
sort_dir = 'asc'
|
|
|
|
# Ensure a per-column sort direction
|
|
if sort_dirs is None:
|
|
sort_dirs = [sort_dir] * len(sort_keys)
|
|
|
|
assert(len(sort_dirs) == len(sort_keys)) # nosec
|
|
# nosec: This function runs safely if the assertion fails.
|
|
if len(sort_dirs) < len(sort_keys):
|
|
sort_dirs += [sort_dir] * (len(sort_keys) - len(sort_dirs))
|
|
|
|
# Add sorting
|
|
for current_sort_key, current_sort_dir in zip(sort_keys, sort_dirs):
|
|
try:
|
|
sort_dir_func = {
|
|
'asc': sqlalchemy.asc,
|
|
'desc': sqlalchemy.desc,
|
|
}[current_sort_dir]
|
|
except KeyError:
|
|
raise ValueError(_LE("Unknown sort direction, "
|
|
"must be 'desc' or 'asc'"))
|
|
|
|
if current_sort_key[1] is None:
|
|
# sort by generic property
|
|
query = query.order_by(sort_dir_func(getattr(
|
|
models.Artifact,
|
|
current_sort_key[0])))
|
|
else:
|
|
# sort by custom property
|
|
prop_type = current_sort_key[1] + "_value"
|
|
query = (
|
|
query.join(models.ArtifactProperty).
|
|
filter(models.ArtifactProperty.name == current_sort_key[0]).
|
|
order_by(sort_dir_func(getattr(models.ArtifactProperty,
|
|
prop_type))))
|
|
|
|
default = ''
|
|
|
|
# Add pagination
|
|
if marker is not None:
|
|
marker_values = []
|
|
for sort_key in sort_keys:
|
|
v = getattr(marker, sort_key[0])
|
|
if v is None:
|
|
v = default
|
|
marker_values.append(v)
|
|
|
|
# Build up an array of sort criteria as in the docstring
|
|
criteria_list = []
|
|
for i in range(len(sort_keys)):
|
|
crit_attrs = []
|
|
if marker_values[i] is None:
|
|
continue
|
|
for j in range(i):
|
|
if sort_keys[j][1] is None:
|
|
model_attr = getattr(models.Artifact, sort_keys[j][0])
|
|
else:
|
|
model_attr = getattr(models.ArtifactProperty,
|
|
sort_keys[j][1] + "_value")
|
|
default = None if isinstance(
|
|
model_attr.property.columns[0].type,
|
|
sqlalchemy.DateTime) else ''
|
|
attr = case([(model_attr != None,
|
|
model_attr), ],
|
|
else_=default)
|
|
crit_attrs.append((attr == marker_values[j]))
|
|
|
|
if sort_keys[i][1] is None:
|
|
model_attr = getattr(models.Artifact, sort_keys[i][0])
|
|
else:
|
|
model_attr = getattr(models.ArtifactProperty,
|
|
sort_keys[i][1] + "_value")
|
|
|
|
default = None if isinstance(model_attr.property.columns[0].type,
|
|
sqlalchemy.DateTime) else ''
|
|
attr = case([(model_attr != None,
|
|
model_attr), ],
|
|
else_=default)
|
|
|
|
if sort_dirs[i] == 'desc':
|
|
crit_attrs.append((attr < marker_values[i]))
|
|
else:
|
|
crit_attrs.append((attr > marker_values[i]))
|
|
|
|
criteria = and_(*crit_attrs)
|
|
criteria_list.append(criteria)
|
|
|
|
f = or_(*criteria_list)
|
|
query = query.filter(f)
|
|
|
|
if limit is not None:
|
|
query = query.limit(limit)
|
|
|
|
return query
|
|
|
|
|
|
def _do_artifacts_query(context, session, show_level=ga.Showlevel.NONE):
|
|
"""Build the query to get all artifacts based on the context"""
|
|
|
|
LOG.debug("context.is_admin=%(is_admin)s; context.owner=%(owner)s",
|
|
{'is_admin': context.is_admin, 'owner': context.owner})
|
|
|
|
if show_level == ga.Showlevel.NONE:
|
|
query = session.query(models.Artifact).options(
|
|
joinedload(models.Artifact.tags))
|
|
elif show_level == ga.Showlevel.BASIC:
|
|
query = (
|
|
session.query(models.Artifact).
|
|
options(joinedload(
|
|
models.Artifact.properties).
|
|
defer(models.ArtifactProperty.text_value)).
|
|
options(joinedload(models.Artifact.tags)).
|
|
options(joinedload(models.Artifact.blobs).
|
|
joinedload(models.ArtifactBlob.locations)))
|
|
else:
|
|
# other show_levels aren't supported
|
|
msg = _LW("Show level %s is not supported in this "
|
|
"operation") % ga.Showlevel.to_str(show_level)
|
|
LOG.warn(msg)
|
|
raise exception.ArtifactUnsupportedShowLevel(shl=show_level)
|
|
|
|
# If admin, return everything.
|
|
if context.is_admin:
|
|
return query
|
|
else:
|
|
# If regular user, return only public artifacts.
|
|
# However, if context.owner has a value, return both
|
|
# public and private artifacts of the context.owner.
|
|
if context.owner is not None:
|
|
query = query.filter(
|
|
or_(models.Artifact.owner == context.owner,
|
|
models.Artifact.visibility == 'public'))
|
|
else:
|
|
query = query.filter(
|
|
models.Artifact.visibility == 'public')
|
|
return query
|
|
|
|
op_mappings = {
|
|
'EQ': operator.eq,
|
|
'GT': operator.gt,
|
|
'GE': operator.ge,
|
|
'LT': operator.lt,
|
|
'LE': operator.le,
|
|
'NE': operator.ne,
|
|
'IN': operator.eq # it must be eq
|
|
}
|
|
|
|
|
|
def _do_query_filters(filters):
|
|
basic_conds = []
|
|
tag_conds = []
|
|
prop_conds = []
|
|
|
|
# don't show deleted artifacts
|
|
basic_conds.append([models.Artifact.state != 'deleted'])
|
|
|
|
visibility = filters.pop('visibility', None)
|
|
if visibility is not None:
|
|
# ignore operator. always consider it EQ
|
|
basic_conds.append(
|
|
[models.Artifact.visibility == visibility[0]['value']])
|
|
|
|
type_name = filters.pop('type_name', None)
|
|
if type_name is not None:
|
|
# ignore operator. always consider it EQ
|
|
basic_conds.append([models.Artifact.type_name == type_name['value']])
|
|
type_version = filters.pop('type_version', None)
|
|
if type_version is not None:
|
|
# ignore operator. always consider it EQ
|
|
# TODO(mfedosin) add support of LIKE operator
|
|
type_version = semver_db.parse(type_version['value'])
|
|
basic_conds.append([models.Artifact.type_version == type_version])
|
|
|
|
name = filters.pop('name', None)
|
|
if name is not None:
|
|
# ignore operator. always consider it EQ
|
|
basic_conds.append([models.Artifact.name == name[0]['value']])
|
|
|
|
versions = filters.pop('version', None)
|
|
if versions is not None:
|
|
for version in versions:
|
|
value = semver_db.parse(version['value'])
|
|
op = version['operator']
|
|
fn = op_mappings[op]
|
|
basic_conds.append([fn(models.Artifact.version, value)])
|
|
|
|
state = filters.pop('state', None)
|
|
if state is not None:
|
|
# ignore operator. always consider it EQ
|
|
basic_conds.append([models.Artifact.state == state['value']])
|
|
|
|
owner = filters.pop('owner', None)
|
|
if owner is not None:
|
|
# ignore operator. always consider it EQ
|
|
basic_conds.append([models.Artifact.owner == owner[0]['value']])
|
|
|
|
id_list = filters.pop('id_list', None)
|
|
if id_list is not None:
|
|
basic_conds.append([models.Artifact.id.in_(id_list['value'])])
|
|
|
|
name_list = filters.pop('name_list', None)
|
|
if name_list is not None:
|
|
basic_conds.append([models.Artifact.name.in_(name_list['value'])])
|
|
|
|
tags = filters.pop('tags', None)
|
|
if tags is not None:
|
|
for tag in tags:
|
|
tag_conds.append([models.ArtifactTag.value == tag['value']])
|
|
|
|
# process remaining filters
|
|
for filtername, filtervalues in filters.items():
|
|
for filtervalue in filtervalues:
|
|
|
|
db_prop_op = filtervalue['operator']
|
|
db_prop_value = filtervalue['value']
|
|
db_prop_type = filtervalue['type'] + "_value"
|
|
db_prop_position = filtervalue.get('position')
|
|
|
|
conds = [models.ArtifactProperty.name == filtername]
|
|
|
|
if db_prop_op in op_mappings:
|
|
fn = op_mappings[db_prop_op]
|
|
result = fn(getattr(models.ArtifactProperty, db_prop_type),
|
|
db_prop_value)
|
|
|
|
cond = [result]
|
|
if db_prop_position is not 'any':
|
|
cond.append(
|
|
models.ArtifactProperty.position == db_prop_position)
|
|
if db_prop_op == 'IN':
|
|
if (db_prop_position is not None and
|
|
db_prop_position is not 'any'):
|
|
msg = _LE("Cannot use this parameter with "
|
|
"the operator IN")
|
|
LOG.error(msg)
|
|
raise exception.ArtifactInvalidPropertyParameter(
|
|
op='IN')
|
|
cond = [result,
|
|
models.ArtifactProperty.position >= 0]
|
|
else:
|
|
msg = _LE("Operator %s is not supported") % db_prop_op
|
|
LOG.error(msg)
|
|
raise exception.ArtifactUnsupportedPropertyOperator(
|
|
op=db_prop_op)
|
|
|
|
conds.extend(cond)
|
|
|
|
prop_conds.append(conds)
|
|
return basic_conds, tag_conds, prop_conds
|
|
|
|
|
|
def _do_tags(artifact, new_tags):
|
|
tags_to_update = []
|
|
# don't touch existing tags
|
|
for tag in artifact.tags:
|
|
if tag.value in new_tags:
|
|
tags_to_update.append(tag)
|
|
new_tags.remove(tag.value)
|
|
# add new tags
|
|
for tag in new_tags:
|
|
db_tag = models.ArtifactTag()
|
|
db_tag.value = tag
|
|
tags_to_update.append(db_tag)
|
|
return tags_to_update
|
|
|
|
|
|
def _do_property(propname, prop, position=None):
|
|
db_prop = models.ArtifactProperty()
|
|
db_prop.name = propname
|
|
setattr(db_prop,
|
|
(prop['type'] + "_value"),
|
|
prop['value'])
|
|
db_prop.position = position
|
|
return db_prop
|
|
|
|
|
|
def _do_properties(artifact, new_properties):
|
|
|
|
props_to_update = []
|
|
# don't touch existing properties
|
|
for prop in artifact.properties:
|
|
if prop.name not in new_properties:
|
|
props_to_update.append(prop)
|
|
|
|
for propname, prop in new_properties.items():
|
|
if prop['type'] == 'array':
|
|
for pos, arrprop in enumerate(prop['value']):
|
|
props_to_update.append(
|
|
_do_property(propname, arrprop, pos)
|
|
)
|
|
else:
|
|
props_to_update.append(
|
|
_do_property(propname, prop)
|
|
)
|
|
return props_to_update
|
|
|
|
|
|
def _do_blobs(artifact, new_blobs):
|
|
blobs_to_update = []
|
|
|
|
# don't touch existing blobs
|
|
for blob in artifact.blobs:
|
|
if blob.name not in new_blobs:
|
|
blobs_to_update.append(blob)
|
|
|
|
for blobname, blobs in new_blobs.items():
|
|
for pos, blob in enumerate(blobs):
|
|
for db_blob in artifact.blobs:
|
|
if db_blob.name == blobname and db_blob.position == pos:
|
|
# update existing blobs
|
|
db_blob.size = blob['size']
|
|
db_blob.checksum = blob['checksum']
|
|
db_blob.item_key = blob['item_key']
|
|
db_blob.locations = _do_locations(db_blob,
|
|
blob['locations'])
|
|
blobs_to_update.append(db_blob)
|
|
break
|
|
else:
|
|
# create new blob
|
|
db_blob = models.ArtifactBlob()
|
|
db_blob.name = blobname
|
|
db_blob.size = blob['size']
|
|
db_blob.checksum = blob['checksum']
|
|
db_blob.item_key = blob['item_key']
|
|
db_blob.position = pos
|
|
db_blob.locations = _do_locations(db_blob, blob['locations'])
|
|
blobs_to_update.append(db_blob)
|
|
return blobs_to_update
|
|
|
|
|
|
def _do_locations(blob, new_locations):
|
|
locs_to_update = []
|
|
for pos, loc in enumerate(new_locations):
|
|
for db_loc in blob.locations:
|
|
if db_loc.value == loc['value']:
|
|
# update existing location
|
|
db_loc.position = pos
|
|
db_loc.status = loc['status']
|
|
locs_to_update.append(db_loc)
|
|
break
|
|
else:
|
|
# create new location
|
|
db_loc = models.ArtifactBlobLocation()
|
|
db_loc.value = loc['value']
|
|
db_loc.status = loc['status']
|
|
db_loc.position = pos
|
|
locs_to_update.append(db_loc)
|
|
return locs_to_update
|
|
|
|
|
|
def _do_dependencies(artifact, new_dependencies, session):
|
|
deps_to_update = []
|
|
# small check that all dependencies are new
|
|
if artifact.dependencies is not None:
|
|
for db_dep in artifact.dependencies:
|
|
for dep in new_dependencies.keys():
|
|
if db_dep.name == dep:
|
|
msg = _LW("Artifact with the specified type, name "
|
|
"and versions already has the direct "
|
|
"dependency=%s") % dep
|
|
LOG.warn(msg)
|
|
# change values of former dependency
|
|
for dep in artifact.dependencies:
|
|
session.delete(dep)
|
|
artifact.dependencies = []
|
|
for depname, depvalues in new_dependencies.items():
|
|
for pos, depvalue in enumerate(depvalues):
|
|
db_dep = models.ArtifactDependency()
|
|
db_dep.name = depname
|
|
db_dep.artifact_source = artifact.id
|
|
db_dep.artifact_dest = depvalue
|
|
db_dep.artifact_origin = artifact.id
|
|
db_dep.is_direct = True
|
|
db_dep.position = pos
|
|
deps_to_update.append(db_dep)
|
|
artifact.dependencies = deps_to_update
|
|
|
|
|
|
def _do_transitive_dependencies(artifact, session):
|
|
deps_to_update = []
|
|
for dependency in artifact.dependencies:
|
|
depvalue = dependency.artifact_dest
|
|
transitdeps = session.query(models.ArtifactDependency).filter_by(
|
|
artifact_source=depvalue).all()
|
|
for transitdep in transitdeps:
|
|
if not transitdep.is_direct:
|
|
# transitive dependencies are already created
|
|
msg = _LW("Artifact with the specified type, "
|
|
"name and version already has the "
|
|
"direct dependency=%d") % transitdep.id
|
|
LOG.warn(msg)
|
|
raise exception.ArtifactDuplicateTransitiveDependency(
|
|
dep=transitdep.id)
|
|
|
|
db_dep = models.ArtifactDependency()
|
|
db_dep.name = transitdep['name']
|
|
db_dep.artifact_source = artifact.id
|
|
db_dep.artifact_dest = transitdep.artifact_dest
|
|
db_dep.artifact_origin = transitdep.artifact_source
|
|
db_dep.is_direct = False
|
|
db_dep.position = transitdep.position
|
|
deps_to_update.append(db_dep)
|
|
return deps_to_update
|
|
|
|
|
|
def _check_visibility(context, artifact):
|
|
if context.is_admin:
|
|
return True
|
|
|
|
if not artifact.owner:
|
|
return True
|
|
|
|
if artifact.visibility == Visibility.PUBLIC.value:
|
|
return True
|
|
|
|
if artifact.visibility == Visibility.PRIVATE.value:
|
|
if context.owner and context.owner == artifact.owner:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
if artifact.visibility == Visibility.SHARED.value:
|
|
return False
|
|
|
|
return False
|
|
|
|
|
|
def _set_version_fields(values):
|
|
if 'type_version' in values:
|
|
values['type_version'] = semver_db.parse(values['type_version'])
|
|
if 'version' in values:
|
|
values['version'] = semver_db.parse(values['version'])
|
|
|
|
|
|
def _validate_values(values):
|
|
if 'state' in values:
|
|
try:
|
|
State(values['state'])
|
|
except ValueError:
|
|
msg = "Invalid artifact state '%s'" % values['state']
|
|
raise exception.Invalid(msg)
|
|
if 'visibility' in values:
|
|
try:
|
|
Visibility(values['visibility'])
|
|
except ValueError:
|
|
msg = "Invalid artifact visibility '%s'" % values['visibility']
|
|
raise exception.Invalid(msg)
|
|
# TODO(mfedosin): it's an idea to validate tags someday
|
|
# (check that all tags match the regexp)
|
|
|
|
|
|
def _drop_protected_attrs(model_class, values):
|
|
"""
|
|
Removed protected attributes from values dictionary using the models
|
|
__protected_attributes__ field.
|
|
"""
|
|
for attr in model_class.__protected_attributes__:
|
|
if attr in values:
|
|
del values[attr]
|