Instead of sending DB IDs, send "internal IDs" which are DB IDs hashed. This way, we can still calculate the ancestors without hitting the DB further, but without leaking the size of the images table
This commit is contained in:
parent
74c1662f54
commit
9621566d31
7 changed files with 59 additions and 36 deletions
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import datetime
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
|
||||||
from flask import Blueprint, request, make_response, jsonify, session
|
from flask import Blueprint, request, make_response, jsonify, session
|
||||||
from flask.ext.restful import Resource, abort, Api, reqparse
|
from flask.ext.restful import Resource, abort, Api, reqparse
|
||||||
|
@ -344,6 +345,12 @@ def log_action(kind, user_or_orgname, metadata=None, repo=None):
|
||||||
metadata=metadata, repository=repo)
|
metadata=metadata, repository=repo)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_internal_id(dbid):
|
||||||
|
""" Returns an 'internal id' we can send to the frontend that represents the
|
||||||
|
given database ID, but without leaking its actual value.
|
||||||
|
"""
|
||||||
|
return hashlib.sha1("internal-db-" + str(dbid)).hexdigest()
|
||||||
|
|
||||||
import endpoints.api.billing
|
import endpoints.api.billing
|
||||||
import endpoints.api.build
|
import endpoints.api.build
|
||||||
import endpoints.api.discovery
|
import endpoints.api.discovery
|
||||||
|
|
|
@ -4,7 +4,7 @@ from collections import defaultdict
|
||||||
|
|
||||||
from app import storage as store
|
from app import storage as store
|
||||||
from endpoints.api import (resource, nickname, require_repo_read, RepositoryParamResource,
|
from endpoints.api import (resource, nickname, require_repo_read, RepositoryParamResource,
|
||||||
format_date, NotFound)
|
format_date, NotFound, calculate_internal_id)
|
||||||
from data import model
|
from data import model
|
||||||
from util.cache import cache_control_flask_restful
|
from util.cache import cache_control_flask_restful
|
||||||
|
|
||||||
|
@ -15,16 +15,31 @@ def image_view(image):
|
||||||
extended_props = image.storage
|
extended_props = image.storage
|
||||||
|
|
||||||
command = extended_props.command
|
command = extended_props.command
|
||||||
|
|
||||||
|
def internal_id(aid):
|
||||||
|
if aid == '':
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return calculate_internal_id(aid)
|
||||||
|
|
||||||
|
# Calculate the ancestors string, with the DBID's replaced with the
|
||||||
|
# hashed 'internal' IDs.
|
||||||
|
ancestors = [internal_id(a) for a in image.ancestors.split('/')]
|
||||||
|
ancestors_string = '/'.join(ancestors)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': image.docker_image_id,
|
'id': image.docker_image_id,
|
||||||
'created': format_date(extended_props.created),
|
'created': format_date(extended_props.created),
|
||||||
'comment': extended_props.comment,
|
'comment': extended_props.comment,
|
||||||
'command': json.loads(command) if command else None,
|
'command': json.loads(command) if command else None,
|
||||||
'ancestors': image.ancestors,
|
|
||||||
'dbid': image.id,
|
|
||||||
'size': extended_props.image_size,
|
'size': extended_props.image_size,
|
||||||
'locations': list(image.storage.locations),
|
'locations': list(image.storage.locations),
|
||||||
'uploading': image.storage.uploading,
|
'uploading': image.storage.uploading,
|
||||||
|
|
||||||
|
'ancestors': ancestors_string,
|
||||||
|
|
||||||
|
'internal_id': calculate_internal_id(image.id),
|
||||||
|
'sort_index': len(image.ancestors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@ from data import model
|
||||||
from endpoints.api import (truthy_bool, format_date, nickname, log_action, validate_json_request,
|
from endpoints.api import (truthy_bool, format_date, nickname, log_action, validate_json_request,
|
||||||
require_repo_read, require_repo_write, require_repo_admin,
|
require_repo_read, require_repo_write, require_repo_admin,
|
||||||
RepositoryParamResource, resource, query_param, parse_args, ApiResource,
|
RepositoryParamResource, resource, query_param, parse_args, ApiResource,
|
||||||
request_error, require_scope, Unauthorized, NotFound, InvalidRequest)
|
request_error, require_scope, Unauthorized, NotFound, InvalidRequest,
|
||||||
|
calculate_internal_id)
|
||||||
from auth.permissions import (ModifyRepositoryPermission, AdministerRepositoryPermission,
|
from auth.permissions import (ModifyRepositoryPermission, AdministerRepositoryPermission,
|
||||||
CreateRepositoryPermission, ReadRepositoryPermission)
|
CreateRepositoryPermission, ReadRepositoryPermission)
|
||||||
from auth.auth_context import get_authenticated_user
|
from auth.auth_context import get_authenticated_user
|
||||||
|
@ -169,7 +170,7 @@ class Repository(RepositoryParamResource):
|
||||||
return {
|
return {
|
||||||
'name': tag.name,
|
'name': tag.name,
|
||||||
'image_id': tag.image.docker_image_id,
|
'image_id': tag.image.docker_image_id,
|
||||||
'dbid': tag.image.id
|
'internal_id': calculate_internal_id(tag.image.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
organization = None
|
organization = None
|
||||||
|
|
|
@ -6058,7 +6058,7 @@ quayApp.directive('tagSpecificImagesView', function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentTag = $scope.repository.tags[$scope.tag];
|
var currentTag = $scope.repository.tags[$scope.tag];
|
||||||
if (image.dbid == currentTag.dbid) {
|
if (image.internal_id == currentTag.internal_id) {
|
||||||
classes += 'tag-image ';
|
classes += 'tag-image ';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6068,15 +6068,15 @@ quayApp.directive('tagSpecificImagesView', function () {
|
||||||
var forAllTagImages = function(tag, callback, opt_cutoff) {
|
var forAllTagImages = function(tag, callback, opt_cutoff) {
|
||||||
if (!tag) { return; }
|
if (!tag) { return; }
|
||||||
|
|
||||||
if (!$scope.imageByDBID) {
|
if (!$scope.imageByInternalId) {
|
||||||
$scope.imageByDBID = [];
|
$scope.imageByInternalId = [];
|
||||||
for (var i = 0; i < $scope.images.length; ++i) {
|
for (var i = 0; i < $scope.images.length; ++i) {
|
||||||
var currentImage = $scope.images[i];
|
var currentImage = $scope.images[i];
|
||||||
$scope.imageByDBID[currentImage.dbid] = currentImage;
|
$scope.imageByInternalId[currentImage.internal_id] = currentImage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tag_image = $scope.imageByDBID[tag.dbid];
|
var tag_image = $scope.imageByInternalId[tag.internal_id];
|
||||||
if (!tag_image) {
|
if (!tag_image) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -6085,7 +6085,7 @@ quayApp.directive('tagSpecificImagesView', function () {
|
||||||
|
|
||||||
var ancestors = tag_image.ancestors.split('/').reverse();
|
var ancestors = tag_image.ancestors.split('/').reverse();
|
||||||
for (var i = 0; i < ancestors.length; ++i) {
|
for (var i = 0; i < ancestors.length; ++i) {
|
||||||
var image = $scope.imageByDBID[ancestors[i]];
|
var image = $scope.imageByInternalId[ancestors[i]];
|
||||||
if (image) {
|
if (image) {
|
||||||
if (image == opt_cutoff) {
|
if (image == opt_cutoff) {
|
||||||
return;
|
return;
|
||||||
|
@ -6111,7 +6111,7 @@ quayApp.directive('tagSpecificImagesView', function () {
|
||||||
var getIdsForTag = function(currentTag) {
|
var getIdsForTag = function(currentTag) {
|
||||||
var ids = {};
|
var ids = {};
|
||||||
forAllTagImages(currentTag, function(image) {
|
forAllTagImages(currentTag, function(image) {
|
||||||
ids[image.dbid] = true;
|
ids[image.internal_id] = true;
|
||||||
}, $scope.imageCutoff);
|
}, $scope.imageCutoff);
|
||||||
return ids;
|
return ids;
|
||||||
};
|
};
|
||||||
|
@ -6121,8 +6121,8 @@ quayApp.directive('tagSpecificImagesView', function () {
|
||||||
for (var currentTagName in $scope.repository.tags) {
|
for (var currentTagName in $scope.repository.tags) {
|
||||||
var currentTag = $scope.repository.tags[currentTagName];
|
var currentTag = $scope.repository.tags[currentTagName];
|
||||||
if (currentTag != tag) {
|
if (currentTag != tag) {
|
||||||
for (var dbid in getIdsForTag(currentTag)) {
|
for (var internal_id in getIdsForTag(currentTag)) {
|
||||||
delete toDelete[dbid];
|
delete toDelete[internal_id];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6131,7 +6131,7 @@ quayApp.directive('tagSpecificImagesView', function () {
|
||||||
var images = [];
|
var images = [];
|
||||||
for (var i = 0; i < $scope.images.length; ++i) {
|
for (var i = 0; i < $scope.images.length; ++i) {
|
||||||
var image = $scope.images[i];
|
var image = $scope.images[i];
|
||||||
if (toDelete[image.dbid]) {
|
if (toDelete[image.internal_id]) {
|
||||||
images.push(image);
|
images.push(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6142,7 +6142,7 @@ quayApp.directive('tagSpecificImagesView', function () {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.dbid - a.dbid;
|
return b.sort_index - a.sort_index;
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.tagSpecificImages = images;
|
$scope.tagSpecificImages = images;
|
||||||
|
|
|
@ -497,7 +497,7 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.findImageForTag = function(tag) {
|
$scope.findImageForTag = function(tag) {
|
||||||
return tag && $scope.imageByDBID && $scope.imageByDBID[tag.dbid];
|
return tag && $scope.imageByInternalId && $scope.imageByInternalId[tag.internal_id];
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.createOrMoveTag = function(image, tagName, opt_invalid) {
|
$scope.createOrMoveTag = function(image, tagName, opt_invalid) {
|
||||||
|
@ -685,9 +685,9 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
|
||||||
};
|
};
|
||||||
|
|
||||||
var forAllTagImages = function(tag, callback) {
|
var forAllTagImages = function(tag, callback) {
|
||||||
if (!tag || !$scope.imageByDBID) { return; }
|
if (!tag || !$scope.imageByInternalId) { return; }
|
||||||
|
|
||||||
var tag_image = $scope.imageByDBID[tag.dbid];
|
var tag_image = $scope.imageByInternalId[tag.internal_id];
|
||||||
if (!tag_image) { return; }
|
if (!tag_image) { return; }
|
||||||
|
|
||||||
// Callback the tag's image itself.
|
// Callback the tag's image itself.
|
||||||
|
@ -697,7 +697,7 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
|
||||||
if (!tag_image.ancestors) { return; }
|
if (!tag_image.ancestors) { return; }
|
||||||
var ancestors = tag_image.ancestors.split('/');
|
var ancestors = tag_image.ancestors.split('/');
|
||||||
for (var i = 0; i < ancestors.length; ++i) {
|
for (var i = 0; i < ancestors.length; ++i) {
|
||||||
var image = $scope.imageByDBID[ancestors[i]];
|
var image = $scope.imageByInternalId[ancestors[i]];
|
||||||
if (image) {
|
if (image) {
|
||||||
callback(image);
|
callback(image);
|
||||||
}
|
}
|
||||||
|
@ -786,10 +786,10 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
|
||||||
$scope.specificImages = [];
|
$scope.specificImages = [];
|
||||||
|
|
||||||
// Build various images for quick lookup of images.
|
// Build various images for quick lookup of images.
|
||||||
$scope.imageByDBID = {};
|
$scope.imageByInternalId = {};
|
||||||
for (var i = 0; i < $scope.images.length; ++i) {
|
for (var i = 0; i < $scope.images.length; ++i) {
|
||||||
var currentImage = $scope.images[i];
|
var currentImage = $scope.images[i];
|
||||||
$scope.imageByDBID[currentImage.dbid] = currentImage;
|
$scope.imageByInternalId[currentImage.internal_id] = currentImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispose of any existing tree.
|
// Dispose of any existing tree.
|
||||||
|
|
|
@ -307,8 +307,8 @@ ImageHistoryTree.prototype.setHighlightedPath_ = function(image) {
|
||||||
this.markPath_(this.currentNode_, false);
|
this.markPath_(this.currentNode_, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var imageByDBID = this.imageByDBID_;
|
var imageByInternalId = this.imageByInternalId_;
|
||||||
var currentNode = imageByDBID[image.dbid];
|
var currentNode = imageByInternalId[image.internal_id];
|
||||||
if (currentNode) {
|
if (currentNode) {
|
||||||
this.markPath_(currentNode, true);
|
this.markPath_(currentNode, true);
|
||||||
this.currentNode_ = currentNode;
|
this.currentNode_ = currentNode;
|
||||||
|
@ -386,7 +386,7 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
|
||||||
var formatted = {"name": "No images found"};
|
var formatted = {"name": "No images found"};
|
||||||
|
|
||||||
// Build a node for each image.
|
// Build a node for each image.
|
||||||
var imageByDBID = {};
|
var imageByInternalId = {};
|
||||||
for (var i = 0; i < this.images_.length; ++i) {
|
for (var i = 0; i < this.images_.length; ++i) {
|
||||||
var image = this.images_[i];
|
var image = this.images_[i];
|
||||||
var imageNode = {
|
var imageNode = {
|
||||||
|
@ -395,9 +395,9 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
|
||||||
"image": image,
|
"image": image,
|
||||||
"tags": image.tags
|
"tags": image.tags
|
||||||
};
|
};
|
||||||
imageByDBID[image.dbid] = imageNode;
|
imageByInternalId[image.internal_id] = imageNode;
|
||||||
}
|
}
|
||||||
this.imageByDBID_ = imageByDBID;
|
this.imageByInternalId_ = imageByInternalId;
|
||||||
|
|
||||||
// For each node, attach it to its immediate parent. If there is no immediate parent,
|
// For each node, attach it to its immediate parent. If there is no immediate parent,
|
||||||
// then the node is the root.
|
// then the node is the root.
|
||||||
|
@ -408,10 +408,10 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
|
||||||
// Skip images that are currently uploading.
|
// Skip images that are currently uploading.
|
||||||
if (image.uploading) { continue; }
|
if (image.uploading) { continue; }
|
||||||
|
|
||||||
var imageNode = imageByDBID[image.dbid];
|
var imageNode = imageByInternalId[image.internal_id];
|
||||||
var ancestors = this.getAncestors_(image);
|
var ancestors = this.getAncestors_(image);
|
||||||
var immediateParent = ancestors[ancestors.length - 1] * 1;
|
var immediateParent = ancestors[ancestors.length - 1];
|
||||||
var parent = imageByDBID[immediateParent];
|
var parent = imageByInternalId[immediateParent];
|
||||||
if (parent) {
|
if (parent) {
|
||||||
// Add a reference to the parent. This makes walking the tree later easier.
|
// Add a reference to the parent. This makes walking the tree later easier.
|
||||||
imageNode.parent = parent;
|
imageNode.parent = parent;
|
||||||
|
@ -442,7 +442,7 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
|
||||||
// Skip images that are currently uploading.
|
// Skip images that are currently uploading.
|
||||||
if (image.uploading) { continue; }
|
if (image.uploading) { continue; }
|
||||||
|
|
||||||
var imageNode = imageByDBID[image.dbid];
|
var imageNode = imageByInternalId[image.internal_id];
|
||||||
maxChildCount = Math.max(maxChildCount, this.determineMaximumChildCount_(imageNode));
|
maxChildCount = Math.max(maxChildCount, this.determineMaximumChildCount_(imageNode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,7 +573,7 @@ ImageHistoryTree.prototype.setTag_ = function(tagName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var imageByDBID = this.imageByDBID_;
|
var imageByInternalId = this.imageByInternalId_;
|
||||||
|
|
||||||
// Save the current tag.
|
// Save the current tag.
|
||||||
var previousTagName = this.currentTag_;
|
var previousTagName = this.currentTag_;
|
||||||
|
@ -596,10 +596,10 @@ ImageHistoryTree.prototype.setTag_ = function(tagName) {
|
||||||
// Skip images that are currently uploading.
|
// Skip images that are currently uploading.
|
||||||
if (image.uploading) { continue; }
|
if (image.uploading) { continue; }
|
||||||
|
|
||||||
var imageNode = this.imageByDBID_[image.dbid];
|
var imageNode = this.imageByInternalId_[image.internal_id];
|
||||||
var ancestors = this.getAncestors_(image);
|
var ancestors = this.getAncestors_(image);
|
||||||
var immediateParent = ancestors[ancestors.length - 1] * 1;
|
var immediateParent = ancestors[ancestors.length - 1];
|
||||||
var parent = imageByDBID[immediateParent];
|
var parent = imageByInternalId[immediateParent];
|
||||||
if (parent && imageNode.highlighted) {
|
if (parent && imageNode.highlighted) {
|
||||||
var arr = parent.children;
|
var arr = parent.children;
|
||||||
if (parent._children) {
|
if (parent._children) {
|
||||||
|
|
|
@ -1436,7 +1436,7 @@ class TestListAndGetImage(ApiTestCase):
|
||||||
assert 'comment' in image
|
assert 'comment' in image
|
||||||
assert 'command' in image
|
assert 'command' in image
|
||||||
assert 'ancestors' in image
|
assert 'ancestors' in image
|
||||||
assert 'dbid' in image
|
assert 'internal_id' in image
|
||||||
assert 'size' in image
|
assert 'size' in image
|
||||||
|
|
||||||
ijson = self.getJsonResponse(RepositoryImage,
|
ijson = self.getJsonResponse(RepositoryImage,
|
||||||
|
|
Reference in a new issue