Note: If an archive, the Dockerfile must be in the root directory.
+
+ Reading Dockerfile:
+
-
+
Base Image Pull Credentials:
-
-
-
-
-
+
+
+ Warning: Robot account {{ pullEntity.name }} does not have
+ read permission on repository {{ privateBaseRepository }}, so
+ this build will fail with an authorization error.
-
-
-
-
+
+
+ The selected Dockerfile contains a FROM that refers to the private
+ repository {{ privateBaseRepository }}.
+ A robot account with read access to that repository is required for the build.
diff --git a/static/js/directives/file-present.js b/static/js/directives/file-present.js
index a692ac167..4789f3b95 100644
--- a/static/js/directives/file-present.js
+++ b/static/js/directives/file-present.js
@@ -15,4 +15,23 @@ angular.module('quay').directive("filePresent", [function () {
});
}
}
+}]);
+
+/**
+ * Raises the 'filesChanged' event on the scope if a file on the marked exists.
+ */
+angular.module('quay').directive("filesChanged", [function () {
+ return {
+ restrict: 'A',
+ scope: {
+ 'filesChanged': "&"
+ },
+ link: function (scope, element, attributes) {
+ element.bind("change", function (changeEvent) {
+ scope.$apply(function() {
+ scope.filesChanged({'files': changeEvent.target.files});
+ });
+ });
+ }
+ }
}]);
\ No newline at end of file
diff --git a/static/js/directives/ui/dockerfile-build-form.js b/static/js/directives/ui/dockerfile-build-form.js
index 76e284f8c..5ce6b6aec 100644
--- a/static/js/directives/ui/dockerfile-build-form.js
+++ b/static/js/directives/ui/dockerfile-build-form.js
@@ -20,10 +20,66 @@ angular.module('quay').directive('dockerfileBuildForm', function () {
'uploading': '=uploading',
'building': '=building'
},
- controller: function($scope, $element, ApiService) {
- $scope.internal = {'hasDockerfile': false};
- $scope.pull_entity = null;
- $scope.is_public = true;
+ controller: function($scope, $element, ApiService, DockerfileService, Config) {
+ var MEGABYTE = 1000000;
+ var MAX_FILE_SIZE = 100 * MEGABYTE;
+
+ $scope.hasDockerFile = false;
+ $scope.pullEntity = null;
+ $scope.dockerfileState = 'none';
+ $scope.privateBaseRepository = null;
+ $scope.isReady = false;
+
+ $scope.handleFilesChanged = function(files) {
+ $scope.dockerfileError = '';
+ $scope.privateBaseRepository = null;
+ $scope.pullEntity = null;
+
+ $scope.dockerfileState = 'loading';
+ $scope.hasDockerFile = files.length > 0;
+
+ var checkPrivateImage = function(baseImage) {
+ var params = {
+ 'repository': baseImage
+ };
+
+ ApiService.getRepo(null, params).then(function(repository) {
+ $scope.privateBaseRepository = repository.is_public ? null : baseImage;
+ $scope.dockerfileState = 'ready';
+ }, function() {
+ $scope.privateBaseRepository = baseImage;
+ $scope.dockerfileState = 'ready';
+ });
+ };
+
+ var loadError = function(msg) {
+ $scope.$apply(function() {
+ $scope.dockerfileError = msg || 'Could not read uploaded Dockerfile';
+ $scope.dockerfileState = 'error';
+ });
+ };
+
+ var gotDockerfile = function(df) {
+ $scope.$apply(function() {
+ var baseImage = df.getRegistryBaseImage();
+ if (baseImage) {
+ checkPrivateImage(baseImage);
+ } else {
+ $scope.dockerfileState = 'ready';
+ }
+ });
+ };
+
+ if (files.length > 0) {
+ if (files[0].size < MAX_FILE_SIZE) {
+ DockerfileService.getDockerfile(files[0], gotDockerfile, loadError);
+ } else {
+ $scope.dockerfileState = 'ready';
+ }
+ } else {
+ $scope.dockerfileState = 'none';
+ }
+ };
var handleBuildFailed = function(message) {
message = message || 'Dockerfile build failed to start';
@@ -97,8 +153,8 @@ angular.module('quay').directive('dockerfileBuildForm', function () {
'file_id': fileId
};
- if (!$scope.is_public && $scope.pull_entity) {
- data['pull_robot'] = $scope.pull_entity['name'];
+ if ($scope.pullEntity) {
+ data['pull_robot'] = $scope.pullEntity['name'];
}
var params = {
@@ -180,13 +236,28 @@ angular.module('quay').directive('dockerfileBuildForm', function () {
});
};
- var checkIsReady = function() {
- $scope.isReady = $scope.internal.hasDockerfile && ($scope.is_public || $scope.pull_entity);
+ var checkReady = function() {
+ $scope.isReady = ($scope.dockerfileState == 'ready' &&
+ (!$scope.privateBaseRepository || $scope.pullEntity));
};
- $scope.$watch('pull_entity', checkIsReady);
- $scope.$watch('is_public', checkIsReady);
- $scope.$watch('internal.hasDockerfile', checkIsReady);
+ var checkEntity = function() {
+ $scope.currentRobotHasPermission = null;
+ if (!$scope.pullEntity) { return; }
+
+ var permParams = {
+ 'repository': $scope.privateBaseRepository,
+ 'username': $scope.pullEntity.name
+ };
+
+ ApiService.getUserTransitivePermission(null, permParams).then(function(resp) {
+ $scope.currentRobotHasPermission = resp['permissions'].length > 0;
+ });
+ };
+
+ $scope.$watch('pullEntity', checkEntity);
+ $scope.$watch('pullEntity', checkReady);
+ $scope.$watch('dockerfileState', checkReady);
$scope.$watch('startNow', function() {
if ($scope.startNow && $scope.repository && !$scope.uploading && !$scope.building) {
diff --git a/static/js/services/dockerfile-service.js b/static/js/services/dockerfile-service.js
new file mode 100644
index 000000000..c6f1d199f
--- /dev/null
+++ b/static/js/services/dockerfile-service.js
@@ -0,0 +1,98 @@
+/**
+ * Service which provides helper methods for extracting information out from a Dockerfile
+ * or an archive containing a Dockerfile.
+ */
+angular.module('quay').factory('DockerfileService', ['DataFileService', 'Config', function(DataFileService, Config) {
+ var dockerfileService = {};
+
+ function DockerfileInfo(contents) {
+ this.contents = contents;
+ }
+
+ DockerfileInfo.prototype.getRegistryBaseImage = function() {
+ var baseImage = this.getBaseImage();
+ if (!baseImage) {
+ return;
+ }
+
+ if (baseImage.indexOf(Config.getDomain() + '/') != 0) {
+ return;
+ }
+
+ return baseImage.substring(Config.getDomain().length + 1);
+ };
+
+ DockerfileInfo.prototype.getBaseImage = function() {
+ var fromIndex = this.contents.indexOf('FROM ');
+ if (fromIndex < 0) {
+ return;
+ }
+
+ var newline = this.contents.indexOf('\n', fromIndex);
+ if (newline < 0) {
+ newline = this.contents.length;
+ }
+
+ return $.trim(this.contents.substring(fromIndex + 'FROM '.length, newline));
+ };
+
+ DockerfileInfo.forData = function(contents) {
+ if (contents.indexOf('FROM ') < 0) {
+ return;
+ }
+
+ return new DockerfileInfo(contents);
+ };
+
+ var processFiles = function(files, dataArray, success, failure) {
+ // The files array will be empty if the submitted file was not an archive. We therefore
+ // treat it as a single Dockerfile.
+ if (files.length == 0) {
+ DataFileService.arrayToString(dataArray, function(c) {
+ var result = DockerfileInfo.forData(c);
+ if (!result) {
+ failure('File chosen is not a valid Dockerfile');
+ return;
+ }
+
+ success(result);
+ });
+ return;
+ }
+
+ var found = false;
+ files.forEach(function(file) {
+ if (file['name'] == 'Dockerfile') {
+ DataFileService.blobToString(file.toBlob(), function(c) {
+ var result = DockerfileInfo.forData(c);
+ if (!result) {
+ failure('Dockerfile inside archive is not a valid Dockerfile');
+ return;
+ }
+
+ success(result);
+ });
+ found = true;
+ }
+ });
+
+ if (!found) {
+ failure('No Dockerfile found in root of archive');
+ }
+ };
+
+ dockerfileService.getDockerfile = function(file, success, failure) {
+ var reader = new FileReader();
+ reader.onload = function(e) {
+ var dataArray = reader.result;
+ DataFileService.readDataArrayAsPossibleArchive(dataArray, function(files) {
+ processFiles(files, dataArray, success, failure);
+ }, failure);
+ };
+
+ reader.onerror = failure;
+ reader.readAsArrayBuffer(file);
+ };
+
+ return dockerfileService;
+}]);
\ No newline at end of file