/** * An element which displays a form for manually starting a dockerfile build. */ angular.module('quay').directive('dockerfileBuildForm', function () { var directiveDefinitionObject = { priority: 0, templateUrl: '/static/directives/dockerfile-build-form.html', replace: false, transclude: false, restrict: 'C', scope: { 'repository': '=repository', 'startNow': '=startNow', 'isReady': '=isReady', 'uploadFailed': '&uploadFailed', 'uploadStarted': '&uploadStarted', 'buildStarted': '&buildStarted', 'buildFailed': '&buildFailed', 'missingFile': '&missingFile', 'uploading': '=uploading', 'building': '=building' }, controller: function($scope, $element, ApiService, DockerfileService, Config) { var MEGABYTE = 1000000; var MAX_FILE_SIZE = 100 * MEGABYTE; var resetState = function() { $scope.hasDockerFile = false; $scope.pullEntity = null; $scope.dockerfileState = 'none'; $scope.privateBaseRepository = null; $scope.isReady = false; }; resetState(); $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'; var result = false; if ($scope.buildFailed) { result = $scope.buildFailed({'message': message}); } if (!result) { bootbox.dialog({ "message": message, "title": "Cannot start Dockerfile build", "buttons": { "close": { "label": "Close", "className": "btn-primary" } } }); } }; var handleUploadFailed = function(message) { message = message || 'Error with file upload'; var result = false; if ($scope.uploadFailed) { result = $scope.uploadFailed({'message': message}); } if (!result) { bootbox.dialog({ "message": message, "title": "Cannot upload file for Dockerfile build", "buttons": { "close": { "label": "Close", "className": "btn-primary" } } }); } }; var handleMissingFile = function() { var result = false; if ($scope.missingFile) { result = $scope.missingFile({}); } if (!result) { bootbox.dialog({ "message": 'A Dockerfile or an archive containing a Dockerfile is required', "title": "Missing Dockerfile", "buttons": { "close": { "label": "Close", "className": "btn-primary" } } }); } }; var startBuild = function(fileId) { $scope.building = true; var repo = $scope.repository; var data = { 'file_id': fileId }; if ($scope.pullEntity) { data['pull_robot'] = $scope.pullEntity['name']; } var params = { 'repository': repo.namespace + '/' + repo.name, }; ApiService.requestRepoBuild(data, params).then(function(resp) { $scope.building = false; $scope.uploading = false; if ($scope.buildStarted) { $scope.buildStarted({'build': resp}); } }, function(resp) { $scope.building = false; $scope.uploading = false; handleBuildFailed(resp.message); }); }; var conductUpload = function(file, url, fileId, mimeType) { if ($scope.uploadStarted) { $scope.uploadStarted({}); } var request = new XMLHttpRequest(); request.open('PUT', url, true); request.setRequestHeader('Content-Type', mimeType); request.onprogress = function(e) { $scope.$apply(function() { var percentLoaded; if (e.lengthComputable) { $scope.upload_progress = (e.loaded / e.total) * 100; } }); }; request.onerror = function() { $scope.$apply(function() { handleUploadFailed(); }); }; request.onreadystatechange = function() { var state = request.readyState; var status = request.status; if (state == 4) { if (Math.floor(status / 100) == 2) { $scope.$apply(function() { startBuild(fileId); $scope.uploading = false; }); } else { var message = request.statusText; if (status == 413) { message = 'Selected file too large to upload'; } $scope.$apply(function() { handleUploadFailed(message); }); } } }; request.send(file); }; var startFileUpload = function(repo) { $scope.uploading = true; $scope.uploading_progress = 0; var uploader = $element.find('#file-drop')[0]; if (uploader.files.length == 0) { handleMissingFile(); $scope.uploading = false; return; } var file = uploader.files[0]; $scope.upload_file = file.name; var mimeType = file.type || 'application/octet-stream'; var data = { 'mimeType': mimeType }; var getUploadUrl = ApiService.getFiledropUrl(data).then(function(resp) { conductUpload(file, resp.url, resp.file_id, mimeType); }, function() { handleUploadFailed('Could not retrieve upload URL'); }); }; var checkReady = function() { $scope.isReady = ($scope.dockerfileState == 'ready' && (!$scope.privateBaseRepository || $scope.pullEntity)); }; 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('repository', resetState); $scope.$watch('startNow', function() { if ($scope.startNow && $scope.repository && !$scope.uploading && !$scope.building) { startFileUpload(); } }); } }; return directiveDefinitionObject; });