-
diff --git a/static/js/app.js b/static/js/app.js
index 9ebe2a3e1..1adf4d0b3 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -812,6 +812,15 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
return config['SERVER_HOSTNAME'];
};
+ config.getHost = function(opt_auth) {
+ var auth = opt_auth;
+ if (auth) {
+ auth = auth + '@';
+ }
+
+ return config['PREFERRED_URL_SCHEME'] + '://' + auth + config['SERVER_HOSTNAME'];
+ };
+
config.getUrl = function(opt_path) {
var path = opt_path || '';
return config['PREFERRED_URL_SCHEME'] + '://' + config['SERVER_HOSTNAME'] + path;
@@ -2455,7 +2464,7 @@ quayApp.directive('copyBox', function () {
restrict: 'C',
scope: {
'value': '=value',
- 'hoveringMessage': '=hoveringMessage'
+ 'hoveringMessage': '=hoveringMessage',
},
controller: function($scope, $element, $rootScope) {
$scope.disabled = false;
@@ -2464,7 +2473,7 @@ quayApp.directive('copyBox', function () {
$rootScope.__copyBoxIdCounter = number + 1;
$scope.inputId = "copy-box-input-" + number;
- var button = $($element).find('.input-group-addon');
+ var button = $($element).find('.copy-icon');
var input = $($element).find('input');
input.attr('id', $scope.inputId);
diff --git a/static/js/controllers.js b/static/js/controllers.js
index 7010dc4eb..f781f4dac 100644
--- a/static/js/controllers.js
+++ b/static/js/controllers.js
@@ -361,6 +361,9 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
var namespace = $routeParams.namespace;
var name = $routeParams.name;
+ $scope.pullCommands = [];
+ $scope.currentPullCommand = null;
+
$rootScope.title = 'Loading...';
// Watch for the destruction of the scope.
@@ -395,6 +398,47 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
$scope.buildDialogShowCounter = 0;
$scope.getFormattedCommand = ImageMetadataService.getFormattedCommand;
+ $scope.setCurrentPullCommand = function(pullCommand) {
+ $scope.currentPullCommand = pullCommand;
+ };
+
+ $scope.updatePullCommand = function() {
+ $scope.pullCommands = [];
+
+ if ($scope.currentTag) {
+ $scope.pullCommands.push({
+ 'title': 'docker pull (Tag ' + $scope.currentTag.name + ')',
+ 'shortTitle': 'Pull Tag',
+ 'icon': 'fa-tag',
+ 'command': 'docker pull ' + Config.getDomain() + '/' + namespace + '/' + name + ':' + $scope.currentTag.name
+ });
+ }
+
+ $scope.pullCommands.push({
+ 'title': 'docker pull (Full Repository)',
+ 'shortTitle': 'Pull Repo',
+ 'icon': 'fa-code-fork',
+ 'command': 'docker pull ' + Config.getDomain() + '/' + namespace + '/' + name
+ });
+
+ if ($scope.currentTag) {
+ var squash = 'docker import ' + Config.getHost('ACCOUNTNAME:PASSWORDORTOKEN');
+ squash += '/verbs/v1/' + namespace + '/' + name + '/' + $scope.currentTag.name + '/squash';
+ squash += ' ';
+ squash += Config.getDomain() + '/' + namespace + '/' + name + '/' + $scope.currentTag.name + '.squash';
+
+ $scope.pullCommands.push({
+ 'title': 'Squashed image (Tag ' + $scope.currentTag.name + ')',
+ 'shortTitle': 'Squashed',
+ 'icon': 'fa-file-archive-o',
+ 'command': squash,
+ 'experimental': true
+ });
+ }
+
+ $scope.currentPullCommand = $scope.pullCommands[0];
+ };
+
$scope.showNewBuildDialog = function() {
$scope.buildDialogShowCounter++;
};
@@ -587,6 +631,8 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
$location.search('tag', null);
$location.search('image', imageId.substr(0, 12));
}
+
+ $scope.updatePullCommand();
};
$scope.setTag = function(tagName, opt_updateURL) {
@@ -621,6 +667,8 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
$scope.currentTag = null;
$scope.currentImage = null;
}
+
+ $scope.updatePullCommand();
};
$scope.getFirstTextLine = getFirstTextLine;
diff --git a/static/partials/view-repo.html b/static/partials/view-repo.html
index e5f2cecc6..68be28679 100644
--- a/static/partials/view-repo.html
+++ b/static/partials/view-repo.html
@@ -56,10 +56,21 @@
-
-
+
+
+
+
diff --git a/util/aufs.py b/util/aufs.py
new file mode 100644
index 000000000..e1ffb5b4a
--- /dev/null
+++ b/util/aufs.py
@@ -0,0 +1,31 @@
+import os
+
+AUFS_METADATA = u'.wh..wh.'
+AUFS_WHITEOUT = u'.wh.'
+AUFS_WHITEOUT_PREFIX_LENGTH = len(AUFS_WHITEOUT)
+
+def is_aufs_metadata(filepath):
+ """ Returns whether the given filepath references an AUFS metadata file. """
+ filename = os.path.basename(filepath)
+ return filename.startswith(AUFS_METADATA) or filepath.startswith(AUFS_METADATA)
+
+def get_deleted_filename(filepath):
+ """ Returns the name of the deleted file referenced by the AUFS whiteout file at
+ the given path or None if the file path does not reference a whiteout file.
+ """
+ filename = os.path.basename(filepath)
+ if not filename.startswith(AUFS_WHITEOUT):
+ return None
+
+ return filename[AUFS_WHITEOUT_PREFIX_LENGTH:]
+
+def get_deleted_prefix(filepath):
+ """ Returns the path prefix of the deleted file referenced by the AUFS whiteout file at
+ the given path or None if the file path does not reference a whiteout file.
+ """
+ deleted_filename = get_deleted_filename(filepath)
+ if deleted_filename is None:
+ return None
+
+ dirname = os.path.dirname(filepath)
+ return os.path.join('/', dirname, deleted_filename)
diff --git a/util/changes.py b/util/changes.py
index eaeec9d83..a6d20041f 100644
--- a/util/changes.py
+++ b/util/changes.py
@@ -1,16 +1,10 @@
import marisa_trie
import os
import tarfile
-
-
-AUFS_METADATA = u'.wh..wh.'
-
-AUFS_WHITEOUT = u'.wh.'
-AUFS_WHITEOUT_PREFIX_LENGTH = len(AUFS_WHITEOUT)
+from aufs import is_aufs_metadata, get_deleted_prefix
ALLOWED_TYPES = {tarfile.REGTYPE, tarfile.AREGTYPE}
-
def files_and_dirs_from_tar(source_stream, removed_prefix_collector):
try:
tar_stream = tarfile.open(mode='r|*', fileobj=source_stream)
@@ -20,22 +14,19 @@ def files_and_dirs_from_tar(source_stream, removed_prefix_collector):
for tar_info in tar_stream:
absolute = os.path.relpath(tar_info.name.decode('utf-8'), './')
- dirname = os.path.dirname(absolute)
- filename = os.path.basename(absolute)
- # Skip directories and metadata
- if (filename.startswith(AUFS_METADATA) or
- absolute.startswith(AUFS_METADATA)):
- # Skip
+ # Skip metadata.
+ if is_aufs_metadata(absolute):
continue
- elif filename.startswith(AUFS_WHITEOUT):
- removed_filename = filename[AUFS_WHITEOUT_PREFIX_LENGTH:]
- removed_prefix = os.path.join('/', dirname, removed_filename)
- removed_prefix_collector.add(removed_prefix)
+ # Add prefixes of removed paths to the collector.
+ deleted_prefix = get_deleted_prefix(absolute)
+ if deleted_prefix is not None:
+ deleted_prefix.add(deleted_prefix)
continue
- elif tar_info.type in ALLOWED_TYPES:
+ # Otherwise, yield the path if it is in the allowed types.
+ if tar_info.type in ALLOWED_TYPES:
yield '/' + absolute
diff --git a/util/streamlayerformat.py b/util/streamlayerformat.py
index c197763f1..757d1b4ef 100644
--- a/util/streamlayerformat.py
+++ b/util/streamlayerformat.py
@@ -1,8 +1,8 @@
import marisa_trie
import os
import tarfile
-import StringIO
-import traceback
+from aufs import is_aufs_metadata, get_deleted_prefix
+
AUFS_METADATA = u'.wh..wh.'
@@ -70,19 +70,15 @@ class StreamLayerMerger(object):
def process_tar_info(self, tar_info):
absolute = os.path.relpath(tar_info.name.decode('utf-8'), './')
- dirname = os.path.dirname(absolute)
- filename = os.path.basename(absolute)
- # Skip directories and metadata
- if (filename.startswith(AUFS_METADATA) or
- absolute.startswith(AUFS_METADATA)):
- # Skip
+ # Skip metadata.
+ if is_aufs_metadata(absolute):
return None
- elif filename.startswith(AUFS_WHITEOUT):
- removed_filename = filename[AUFS_WHITEOUT_PREFIX_LENGTH:]
- removed_prefix = os.path.join('/', dirname, removed_filename)
- self.encountered.append(removed_prefix)
+ # Add any prefix of deleted paths to the prefix list.
+ deleted_prefix = get_deleted_prefix(absolute)
+ if deleted_prefix is not None:
+ self.encountered.append(deleted_prefix)
return None
# Check if this file has already been encountered somewhere. If so,
@@ -90,10 +86,6 @@ class StreamLayerMerger(object):
if unicode(absolute) in self.trie:
return None
+ # Otherwise, add the path to the encountered list and return it.
self.encountered.append(absolute)
-
- if tar_info.isdir() or tar_info.issym() or tar_info.islnk():
- return (tar_info, False)
-
- elif tar_info.isfile():
- return (tar_info, True)
+ return (tar_info, tar_info.isfile() or tar_info.isdev())