Merge branch 'laffa' of bitbucket.org:yackob03/quay into laffa
This commit is contained in:
commit
99d57752f5
8 changed files with 197 additions and 79 deletions
|
@ -2206,37 +2206,55 @@ p.editable:hover i {
|
|||
font-size: 0.8em;
|
||||
position: relative;
|
||||
margin-top: 30px;
|
||||
margin-right: 26px;
|
||||
}
|
||||
|
||||
.repo .pull-container {
|
||||
display: inline-block;
|
||||
width: 300px;
|
||||
width: 460px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.repo .pull-container input {
|
||||
cursor: default;
|
||||
background: white;
|
||||
color: #666;
|
||||
padding: 4px;
|
||||
border: 1px solid #ddd;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.repo-image-view .id-container {
|
||||
.repo .pull-container .pull-selector {
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
width: 114px;
|
||||
font-size: 14px;
|
||||
height: 36px;
|
||||
vertical-align: top;
|
||||
border: 1px solid #ddd;
|
||||
margin-right: -3px;
|
||||
background: #f8f8f8;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.repo-image-view .id-container input {
|
||||
background: #fefefe;
|
||||
.repo .pull-container .pull-selector i {
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.repo-image-view .id-container .input-group {
|
||||
width: 542px;
|
||||
|
||||
.repo .pull-container .copy-box {
|
||||
width: 340px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.repo .pull-container .copy-box .copy-container {
|
||||
border-top-left-radius: 0px !important;
|
||||
border-bottom-left-radius: 0px !important;
|
||||
border-left: 0px;
|
||||
}
|
||||
|
||||
.repo .pull-container .dropdown-menu li i.fa {
|
||||
text-align: center;
|
||||
width: 12px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.repo .pull-container sup {
|
||||
margin-left: 4px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.repo-image-view #clipboardCopied {
|
||||
|
@ -2272,25 +2290,45 @@ p.editable:hover i {
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.copy-box-element.disabled .input-group-addon {
|
||||
display: none;
|
||||
.copy-box-element .copy-container {
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid #ddd;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copy-box-element input {
|
||||
border: 0px;
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
.copy-box-element .copy-container .copy-icon {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 10px;
|
||||
display: inline-block;
|
||||
color: #ddd;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: color 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.copy-box-element .copy-container .copy-icon.zeroclipboard-is-hover {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.copy-box-element.disabled input {
|
||||
border-radius: 4px !important;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.copy-box-element.disabled .copy-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.global-zeroclipboard-container embed {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#copyClipboard.zeroclipboard-is-hover, .copy-box-element .zeroclipboard-is-hover {
|
||||
background: #428bca;
|
||||
color: white;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
#clipboardCopied.hovering, .copy-box-element .hovering {
|
||||
.copy-box-element .hovering {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 40px;
|
||||
|
@ -2298,16 +2336,11 @@ p.editable:hover i {
|
|||
z-index: 100;
|
||||
}
|
||||
|
||||
.copy-box-element .id-container {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.copy-box-element input {
|
||||
background-color: white !important;
|
||||
}
|
||||
|
||||
#clipboardCopied, .clipboard-copied-message {
|
||||
.clipboard-copied-message {
|
||||
font-size: 0.8em;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
|
@ -2318,7 +2351,7 @@ p.editable:hover i {
|
|||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#clipboardCopied.animated, .clipboard-copied-message {
|
||||
.clipboard-copied-message {
|
||||
-webkit-animation: fadeOut 4s ease-in-out 0s 1 forwards;
|
||||
-moz-animation: fadeOut 4s ease-in-out 0s 1 forwards;
|
||||
-ms-animation: fadeOut 4s ease-in-out 0s 1 forwards;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
<div class="copy-box-element" ng-class="disabled ? 'disabled' : ''">
|
||||
<div class="id-container">
|
||||
<div class="input-group">
|
||||
<div class="copy-container">
|
||||
<input type="text" class="form-control" value="{{ value }}" readonly>
|
||||
<span class="input-group-addon" data-title="Copy to Clipboard">
|
||||
<i class="fa fa-copy"></i>
|
||||
<span class="copy-icon" data-title="Copy to Clipboard"
|
||||
data-container="body"
|
||||
data-placement="bottom"
|
||||
bs-tooltip>
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -56,10 +56,21 @@
|
|||
|
||||
<!-- Pull Command -->
|
||||
<span class="pull-command visible-md-inline">
|
||||
<div class="pull-container" data-title="Pull repository" bs-tooltip="tooltip.title">
|
||||
<div class="input-group">
|
||||
<div class="copy-box" hovering-message="true" value="'docker pull ' + Config.getDomain() + '/' + repo.namespace + '/' + repo.name"></div>
|
||||
</div>
|
||||
<div class="pull-container" ng-show="currentPullCommand">
|
||||
<button class="pull-selector dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa" ng-class="currentPullCommand.icon"></i>
|
||||
{{ currentPullCommand.shortTitle }}
|
||||
<b class="caret"></b>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="pullCommand in pullCommands">
|
||||
<a href="javascript:void(0)" ng-click="setCurrentPullCommand(pullCommand)"><i class="fa" ng-class="pullCommand.icon"></i>
|
||||
{{ pullCommand.title }}
|
||||
<sup ng-if="pullCommand.experimental">Experimental</sup>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="copy-box" hovering-message="true" value="currentPullCommand.command"></div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
|
31
util/aufs.py
Normal file
31
util/aufs.py
Normal file
|
@ -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)
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
Reference in a new issue