Merge pull request #2724 from coreos-inc/multistage-build-ui

Add support for multistage Docker build in build UI
This commit is contained in:
josephschorr 2017-07-19 09:30:22 -04:00 committed by GitHub
commit 3b73695c04
25 changed files with 229 additions and 450 deletions

View file

@ -9,7 +9,7 @@ angular.module('quay').directive('buildLogCommand', function () {
transclude: false,
restrict: 'C',
scope: {
'command': '=command'
'command': '<command'
},
controller: function($scope, $element) {
$scope.getWithoutStep = function(fullTitle) {
@ -20,6 +20,29 @@ angular.module('quay').directive('buildLogCommand', function () {
return $.trim(fullTitle.substring(colon + 1));
};
$scope.isSecondaryFrom = function(fullTitle) {
if (!fullTitle) { return false; }
var command = $scope.getWithoutStep(fullTitle);
return command.indexOf('FROM ') == 0 && fullTitle.indexOf('Step 1 ') < 0;
};
$scope.fromName = function(fullTitle) {
var command = $scope.getWithoutStep(fullTitle);
if (command.indexOf('FROM ') != 0) {
return null;
}
var parts = command.split(' ');
for (var i = 0; i < parts.length - 1; i++) {
var part = parts[i];
if ($.trim(part) == 'as') {
return parts[i + 1];
}
}
return null;
}
}
};
return directiveDefinitionObject;

View file

@ -1,68 +0,0 @@
/**
* An element which displays a Dockerfile command nicely formatted, with optional link to the
* image (for FROM commands that link to us or to the DockerHub).
*/
angular.module('quay').directive('dockerfileCommand', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/dockerfile-command.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'command': '=command'
},
controller: function($scope, $element, UtilService, Config) {
var registryHandlers = {
'quay.io': function(pieces) {
var rnamespace = pieces[pieces.length - 2];
var rname = pieces[pieces.length - 1].split(':')[0];
return '/repository/' + rnamespace + '/' + rname + '/';
},
'': function(pieces) {
var rnamespace = pieces.length == 1 ? '_' : 'u/' + pieces[0];
var rname = pieces[pieces.length - 1].split(':')[0];
return 'https://registry.hub.docker.com/' + rnamespace + '/' + rname + '/';
}
};
registryHandlers[Config.getDomain()] = registryHandlers['quay.io'];
var kindHandlers = {
'FROM': function(title) {
var pieces = title.split('/');
var registry = pieces.length < 3 ? '' : pieces[0];
if (!registryHandlers[registry]) {
return title;
}
return '<i class="fa fa-hdd-o"></i> <a href="' + registryHandlers[registry](pieces) + '" target="_blank">' + title + '</a>';
}
};
$scope.getCommandKind = function(title) {
var space = title.indexOf(' ');
return title.substring(0, space);
};
$scope.getCommandTitleHtml = function(title) {
var space = title.indexOf(' ');
if (space <= 0) {
return UtilService.textToSafeHtml(title);
}
var kind = $scope.getCommandKind(title);
var sanitized = UtilService.textToSafeHtml(title.substring(space + 1));
var handler = kindHandlers[kind || ''];
if (handler) {
return handler(sanitized);
} else {
return sanitized;
}
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,59 @@
.label.FROM {
border-color: #5bc0de !important;
}
.label.ARG {
border-color: #eaef4d !important;
}
.label.ONBUILD {
border-color: #6813d8 !important;
}
.label.CMD, .label.EXPOSE, .label.ENTRYPOINT {
border-color: #428bca !important;
}
.label.RUN, .label.ADD, .label.COPY {
border-color: #5cb85c !important;
}
.label.ENV, .label.VOLUME, .label.USER, .label.WORKDIR, .label.HEALTHCHECK, .label.STOPSIGNAL, .label.SHELL {
border-color: #f0ad4e !important;
}
.label.MAINTAINER {
border-color: #aaa !important;
}
.dockerfile-command {
position: relative;
}
.dockerfile-command .command-title {
font-family: Consolas, "Lucida Console", Monaco, monospace !important;
padding-left: 90px;
display: inline-block;
}
.dockerfile-command .command-title a {
color: #5bc0de;
font-size: 15px;
}
.dockerfile-command .label {
color: white;
padding-top: 4px;
text-align: right;
margin-right: 4px;
width: 86px;
display: inline-block;
border-right: 4px solid #aaa;
background-color: #333;
position: absolute;
top: 0px;
left: -4px;
}

View file

@ -0,0 +1,7 @@
<span class="dockerfile-command dockerfile-command-element">
<span class="label" ng-class="::$ctrl.getCommandKind($ctrl.command)"
ng-if="::$ctrl.getCommandKind($ctrl.command)">
{{ ::$ctrl.getCommandKind($ctrl.command) }}
</span>
<span class="command-title" ng-bind-html="::$ctrl.getCommandTitleHtml($ctrl.command)"></span>
</span>

View file

@ -0,0 +1,73 @@
import { Input, Component, Inject } from 'ng-metadata/core';
import './dockerfile-command.component.css';
/**
* A component which displays a Dockerfile command, nicely formatted.
*/
@Component({
selector: 'dockerfile-command',
templateUrl: '/static/js/directives/ui/dockerfile-command/dockerfile-command.component.html',
})
export class DockerfileCommandComponent {
@Input('<') public command: string;
private registryHandlers: {[domain: string]: Function};
constructor (@Inject('Config') Config: any, @Inject('UtilService') private utilService: any) {
var registryHandlers = {
'quay.io': function(pieces) {
var rnamespace = pieces[pieces.length - 2];
var rname = pieces[pieces.length - 1].split(':')[0];
return '/repository/' + rnamespace + '/' + rname + '/';
},
'': function(pieces) {
var rnamespace = pieces.length == 1 ? '_' : 'u/' + pieces[0];
var rname = pieces[pieces.length - 1].split(':')[0];
return 'https://registry.hub.docker.com/' + rnamespace + '/' + rname + '/';
}
};
registryHandlers[Config.getDomain()] = registryHandlers['quay.io'];
this.registryHandlers = registryHandlers;
}
private getCommandKind(command: string): string {
if (!command) { return ''; }
var space = command.indexOf(' ');
return command.substring(0, space);
}
private getCommandTitleHtml(command: string): string {
if (!command) { return ''; }
var kindHandlers = {
'FROM': (command) => {
var parts = command.split(' ');
var pieces = parts[0].split('/');
var registry = pieces.length < 3 ? '' : pieces[0];
if (!this.registryHandlers[registry]) {
return command;
}
return '<a href="' + this.registryHandlers[registry](pieces) + '" target="_blank">' + parts[0] + '</a> ' + (parts.splice(1).join(' '));
}
};
var space = command.indexOf(' ');
if (space <= 0) {
return this.utilService.textToSafeHtml(command);
}
var kind = this.getCommandKind(command);
var sanitized = this.utilService.textToSafeHtml(command.substring(space + 1));
var handler = kindHandlers[kind || ''];
if (handler) {
return handler(sanitized);
} else {
return sanitized;
}
}
}

View file

@ -1,38 +0,0 @@
/**
* An element which displays the contents of a Dockerfile in a nicely formatted way.
*/
angular.module('quay').directive('dockerfileView', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/dockerfile-view.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'contents': '=contents'
},
controller: function($scope, $element, UtilService) {
$scope.$watch('contents', function(contents) {
$scope.lines = [];
var lines = contents ? contents.split('\n') : [];
for (var i = 0; i < lines.length; ++i) {
var line = $.trim(lines[i]);
var kind = 'text';
if (line && line[0] == '#') {
kind = 'comment';
} else if (line.match(/^([A-Z]+\s)/)) {
kind = 'command';
}
var lineInfo = {
'text': line,
'kind': kind
};
$scope.lines.push(lineInfo);
}
});
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,5 @@
<div class="image-command-element">
<div class="nondocker-command" ng-if="!$ctrl.getDockerfileCommand($ctrl.command)">{{ ::$ctrl.command.join(' ') }}</div>
<dockerfile-command command="::$ctrl.getDockerfileCommand($ctrl.command)"
ng-if="$ctrl.getDockerfileCommand($ctrl.command)"></dockerfile-command>
</div>

View file

@ -0,0 +1,26 @@
import { Input, Component, Inject } from 'ng-metadata/core';
/**
* A component which displays an image's command, nicely formatted.
*/
@Component({
selector: 'image-command',
templateUrl: '/static/js/directives/ui/image-command/image-command.component.html',
})
export class ImageCommandComponent {
@Input('<') public command: string;
private getDockerfileCommand(command: string[]): string {
if (!command || !command.length) { return ''; }
command = command.join(' ').split(' ');
// ["/bin/sh", "-c", "#(nop)", "RUN", "foo"]
if (command[0] != '/bin/sh' || command[1] != '-c') { return ''; }
if (command[2].trim() != '#(nop)') {
return 'RUN ' + command.slice(2).join(' ');
}
return command.slice(3).join(' ');
};
}

View file

@ -13,7 +13,7 @@ angular.module('quay').directive('imageFeatureView', function () {
'image': '=image',
'isEnabled': '=isEnabled'
},
controller: function($scope, $element, Config, ApiService, VulnerabilityService, ViewArray, ImageMetadataService, TableService) {
controller: function($scope, $element, Config, ApiService, VulnerabilityService, ViewArray, TableService) {
$scope.options = {
'filter': null,
'predicate': 'fixableScore',

View file

@ -1,37 +0,0 @@
/**
* An element which displays sidebar information for a image. Primarily used in the repo view.
*/
angular.module('quay').directive('imageInfoSidebar', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/image-info-sidebar.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'tracker': '=tracker',
'image': '=image',
'imageLoader': '=imageLoader',
'tagSelected': '&tagSelected',
'addTagRequested': '&addTagRequested'
},
controller: function($scope, $element, ImageMetadataService) {
$scope.$watch('image', function(image) {
if (!image || !$scope.tracker) { return; }
$scope.imageData = $scope.tracker.lookupImage(image);
});
$scope.parseDate = function(dateString) {
return Date.parse(dateString);
};
$scope.getTags = function(imageData) {
return $scope.imageLoader.getTagsForImage(imageData);
};
$scope.getFormattedCommand = ImageMetadataService.getFormattedCommand;
}
};
return directiveDefinitionObject;
});

View file

@ -13,9 +13,7 @@ angular.module('quay').directive('imageViewLayer', function () {
'image': '=image',
'images': '=images'
},
controller: function($scope, $element, ImageMetadataService) {
$scope.getDockerfileCommand = ImageMetadataService.getDockerfileCommand;
controller: function($scope, $element) {
$scope.getClass = function() {
var index = $.inArray($scope.image, $scope.images);
if (index < 0) {

View file

@ -13,7 +13,7 @@ angular.module('quay').directive('imageVulnerabilityView', function () {
'image': '=image',
'isEnabled': '=isEnabled'
},
controller: function($scope, $element, $routeParams, Config, ApiService, VulnerabilityService, ViewArray, ImageMetadataService, TableService) {
controller: function($scope, $element, $routeParams, Config, ApiService, VulnerabilityService, ViewArray, TableService) {
$scope.options = {
'filter': null,
'fixableVulns': $routeParams['fixable'] == 'true',