Merge pull request #2303 from charltonaustin/view_build_logs_as_superuser_137910387
feature(superuser panel): ability to view logs
This commit is contained in:
commit
2dfae9e892
10 changed files with 224 additions and 27 deletions
|
@ -359,6 +359,29 @@ class RepositoryBuildStatus(RepositoryParamResource):
|
|||
return build_status_view(build)
|
||||
|
||||
|
||||
def get_logs_or_log_url(build):
|
||||
# If the logs have been archived, just return a URL of the completed archive
|
||||
if build.logs_archived:
|
||||
return {
|
||||
'logs_url': log_archive.get_file_url(build.uuid, requires_cors=True)
|
||||
}
|
||||
start = int(request.args.get('start', 0))
|
||||
|
||||
try:
|
||||
count, logs = build_logs.get_log_entries(build.uuid, start)
|
||||
except BuildStatusRetrievalError:
|
||||
count, logs = (0, [])
|
||||
|
||||
response_obj = {}
|
||||
response_obj.update({
|
||||
'start': start,
|
||||
'total': count,
|
||||
'logs': [log for log in logs],
|
||||
})
|
||||
|
||||
return response_obj
|
||||
|
||||
|
||||
@resource('/v1/repository/<apirepopath:repository>/build/<build_uuid>/logs')
|
||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||
@path_param('build_uuid', 'The UUID of the build')
|
||||
|
@ -368,33 +391,12 @@ class RepositoryBuildLogs(RepositoryParamResource):
|
|||
@nickname('getRepoBuildLogs')
|
||||
def get(self, namespace, repository, build_uuid):
|
||||
""" Return the build logs for the build specified by the build uuid. """
|
||||
response_obj = {}
|
||||
|
||||
build = model.build.get_repository_build(build_uuid)
|
||||
if (not build or build.repository.name != repository or
|
||||
build.repository.namespace_user.username != namespace):
|
||||
raise NotFound()
|
||||
|
||||
# If the logs have been archived, just return a URL of the completed archive
|
||||
if build.logs_archived:
|
||||
return {
|
||||
'logs_url': log_archive.get_file_url(build.uuid, requires_cors=True)
|
||||
}
|
||||
|
||||
start = int(request.args.get('start', 0))
|
||||
|
||||
try:
|
||||
count, logs = build_logs.get_log_entries(build.uuid, start)
|
||||
except BuildStatusRetrievalError:
|
||||
count, logs = (0, [])
|
||||
|
||||
response_obj.update({
|
||||
'start': start,
|
||||
'total': count,
|
||||
'logs': [log for log in logs],
|
||||
})
|
||||
|
||||
return response_obj
|
||||
return get_logs_or_log_url(build)
|
||||
|
||||
|
||||
@resource('/v1/filedrop/')
|
||||
|
|
|
@ -14,17 +14,20 @@ from flask import request, make_response, jsonify
|
|||
import features
|
||||
|
||||
from app import (app, avatar, superusers, authentication, config_provider, license_validator,
|
||||
all_queues)
|
||||
all_queues, log_archive, build_logs)
|
||||
from auth import scopes
|
||||
from auth.auth_context import get_authenticated_user
|
||||
from auth.permissions import SuperUserPermission
|
||||
from data.buildlogs import BuildStatusRetrievalError
|
||||
from endpoints.api import (ApiResource, nickname, resource, validate_json_request,
|
||||
internal_only, require_scope, show_if, parse_args,
|
||||
query_param, abort, require_fresh_login, path_param, verify_not_prod,
|
||||
page_support, log_action, InvalidRequest)
|
||||
from endpoints.api.build import build_status_view, get_logs_or_log_url
|
||||
from endpoints.api.logs import get_logs, get_aggregate_logs
|
||||
from data import model
|
||||
from data.database import ServiceKeyApprovalType
|
||||
from endpoints.exception import NotFound
|
||||
from util.useremails import send_confirmation_email, send_recovery_email
|
||||
from util.license import decode_license, LicenseDecodeError
|
||||
from util.security.ssl import load_certificate, CertInvalidException
|
||||
|
@ -980,3 +983,61 @@ class SuperUserLicense(ApiResource):
|
|||
}
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@resource('/v1/superuser/<build_uuid>/logs')
|
||||
@path_param('build_uuid', 'The UUID of the build')
|
||||
@show_if(features.SUPER_USERS)
|
||||
class SuperUserRepositoryBuildLogs(ApiResource):
|
||||
""" Resource for loading repository build logs for the superuser. """
|
||||
@require_fresh_login
|
||||
@verify_not_prod
|
||||
@nickname('getRepoBuildLogsSuperUser')
|
||||
@require_scope(scopes.SUPERUSER)
|
||||
def get(self, build_uuid):
|
||||
""" Return the build logs for the build specified by the build uuid. """
|
||||
if not SuperUserPermission().can():
|
||||
abort(403)
|
||||
|
||||
return get_logs_or_log_url(model.build.get_repository_build(build_uuid))
|
||||
|
||||
|
||||
@resource('/v1/superuser/<build_uuid>/status')
|
||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||
@path_param('build_uuid', 'The UUID of the build')
|
||||
@show_if(features.SUPER_USERS)
|
||||
class SuperUserRepositoryBuildStatus(ApiResource):
|
||||
""" Resource for dealing with repository build status. """
|
||||
@require_fresh_login
|
||||
@verify_not_prod
|
||||
@nickname('getRepoBuildStatusSuperUser')
|
||||
@require_scope(scopes.SUPERUSER)
|
||||
def get(self, build_uuid):
|
||||
""" Return the status for the builds specified by the build uuids. """
|
||||
if not SuperUserPermission().can():
|
||||
abort(403)
|
||||
build = model.build.get_repository_build(build_uuid)
|
||||
return build_status_view(build)
|
||||
|
||||
|
||||
@resource('/v1/superuser/<build_uuid>/build')
|
||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||
@path_param('build_uuid', 'The UUID of the build')
|
||||
@show_if(features.SUPER_USERS)
|
||||
class SuperUserRepositoryBuildResource(ApiResource):
|
||||
""" Resource for dealing with repository builds as a super user. """
|
||||
@require_fresh_login
|
||||
@verify_not_prod
|
||||
@nickname('getRepoBuildSuperUser')
|
||||
@require_scope(scopes.SUPERUSER)
|
||||
def get(self, build_uuid):
|
||||
""" Returns information about a build. """
|
||||
if not SuperUserPermission().can():
|
||||
abort(403)
|
||||
|
||||
try:
|
||||
build = model.build.get_repository_build(build_uuid)
|
||||
except model.build.InvalidRepositoryBuildException:
|
||||
raise NotFound()
|
||||
|
||||
return build_status_view(build)
|
||||
|
|
|
@ -3797,3 +3797,9 @@ i.mesos-icon {
|
|||
font-size: 16px !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.build-log-view {
|
||||
float: left;
|
||||
padding-right: .5em;
|
||||
}
|
||||
|
||||
|
|
18
static/directives/super-user-build-logs.html
Normal file
18
static/directives/super-user-build-logs.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!-- Messages tab -->
|
||||
<div class="super-user-build-logs-element">
|
||||
<div class="manager-header" header-title="Build Logs">
|
||||
<span class="build-log-view">
|
||||
<input class="form-control" type="text" ng-model="buildParams.buildUuid" placeholder="Build ID"
|
||||
style="margin-right: 10px;">
|
||||
</span>
|
||||
<button class="create-button btn btn-primary" ng-click="loadBuild()">
|
||||
<i class="fa fa-plus" style="margin-right: 6px;"></i>Get Logs
|
||||
</button>
|
||||
</div>
|
||||
<div class="build-logs-view"
|
||||
build="build"
|
||||
use-timestamps="showLogTimestamps"
|
||||
build-updated="setUpdatedBuild(build)"
|
||||
is-super-user="true"
|
||||
ng-show="build"></div>
|
||||
</div>
|
|
@ -11,11 +11,18 @@ angular.module('quay').directive('buildLogsView', function () {
|
|||
scope: {
|
||||
'build': '=build',
|
||||
'useTimestamps': '=useTimestamps',
|
||||
'buildUpdated': '&buildUpdated'
|
||||
'buildUpdated': '&buildUpdated',
|
||||
'isSuperUser': '=isSuperUser'
|
||||
},
|
||||
controller: function($scope, $element, $interval, $sanitize, ansi2html, AngularViewArray,
|
||||
AngularPollChannel, ApiService, Restangular, UtilService) {
|
||||
|
||||
var repoStatusApiCall = ApiService.getRepoBuildStatus;
|
||||
var repoLogApiCall = ApiService.getRepoBuildLogsAsResource;
|
||||
if( $scope.isSuperUser ){
|
||||
repoStatusApiCall = ApiService.getRepoBuildStatusSuperUser;
|
||||
repoLogApiCall = ApiService.getRepoBuildLogsSuperUserAsResource;
|
||||
}
|
||||
var result = $element.find('#copyButton').clipboardCopy();
|
||||
if (!result) {
|
||||
$element.find('#copyButton').hide();
|
||||
|
@ -95,7 +102,8 @@ angular.module('quay').directive('buildLogsView', function () {
|
|||
'build_uuid': build.id
|
||||
};
|
||||
|
||||
ApiService.getRepoBuildStatus(null, params, true).then(function(resp) {
|
||||
|
||||
repoStatusApiCall(null, params, true).then(function(resp) {
|
||||
if (resp.id != $scope.build.id) { callback(false); return; }
|
||||
|
||||
// Call the build updated handler.
|
||||
|
@ -109,7 +117,7 @@ angular.module('quay').directive('buildLogsView', function () {
|
|||
'start': $scope.logStartIndex
|
||||
};
|
||||
|
||||
ApiService.getRepoBuildLogsAsResource(params, true).withOptions(options).get(function(resp) {
|
||||
repoLogApiCall(params, true).withOptions(options).get(function(resp) {
|
||||
// If we get a logs url back, then we need to make another XHR request to retrieve the
|
||||
// data.
|
||||
var logsUrl = resp['logs_url'];
|
||||
|
|
31
static/js/directives/ui/super-user-build-logs.js
Normal file
31
static/js/directives/ui/super-user-build-logs.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* An element for managing global messages.
|
||||
*/
|
||||
angular.module('quay').directive('superUserBuildLogs', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/super-user-build-logs.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'isEnabled': '=isEnabled'
|
||||
},
|
||||
controller: function ($scope, $element, ApiService) {
|
||||
$scope.buildParams = {};
|
||||
$scope.showLogTimestamps = true;
|
||||
$scope.loadBuild = function () {
|
||||
var params = {
|
||||
'build_uuid': $scope.buildParams.buildUuid
|
||||
};
|
||||
ApiService.getRepoBuildSuperUserAsResource(params).get(function (build) {
|
||||
$scope.build = build;
|
||||
});
|
||||
};
|
||||
$scope.setUpdatedBuild = function (build) {
|
||||
$scope.build = build;
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -30,6 +30,7 @@
|
|||
$scope.currentConfig = null;
|
||||
$scope.serviceKeysActive = false;
|
||||
$scope.globalMessagesActive = false;
|
||||
$scope.superUserBuildLogsActive = false;
|
||||
$scope.manageUsersActive = false;
|
||||
$scope.orderedOrgs = [];
|
||||
$scope.orgsPerPage = 10;
|
||||
|
@ -43,6 +44,11 @@
|
|||
$scope.loadMessageOfTheDay = function () {
|
||||
$scope.globalMessagesActive = true;
|
||||
};
|
||||
|
||||
$scope.loadSuperUserBuildLogs = function () {
|
||||
$scope.superUserBuildLogsActive = true;
|
||||
};
|
||||
|
||||
$scope.configurationSaved = function(config) {
|
||||
$scope.currentConfig = config;
|
||||
$scope.requiresRestart = true;
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
build="originalBuild"
|
||||
use-timestamps="showLogTimestamps"
|
||||
build-updated="setUpdatedBuild(build)"
|
||||
is-super-user="false"
|
||||
ng-show="!originalBuild.error"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -54,6 +54,10 @@
|
|||
tab-init="loadMessageOfTheDay()">
|
||||
<i class="fa fa-newspaper-o"></i>
|
||||
</span>
|
||||
<span class="cor-tab hidden-xs" tab-title="Build Logs" tab-target="#super-user-build-logs"
|
||||
tab-init="loadSuperUserBuildLogs()">
|
||||
<i class="fa fa-history"></i>
|
||||
</span>
|
||||
</div> <!-- /cor-tabs -->
|
||||
|
||||
<div class="cor-tab-content">
|
||||
|
@ -66,6 +70,11 @@
|
|||
configuration-saved="configurationSaved(config)"></div>
|
||||
</div>
|
||||
|
||||
<!-- Super user build logs tab-->
|
||||
<div id="super-user-build-logs" class="tab-pane">
|
||||
<div class="super-user-build-logs" is-enabled="superUserBuildLogsActive"></div>
|
||||
</div> <!-- Super user build logs tab -->
|
||||
|
||||
<!-- Messages tab -->
|
||||
<div id="message-of-the-day" class="tab-pane">
|
||||
<div class="global-message-tab" is-enabled="globalMessagesActive"></div>
|
||||
|
|
|
@ -53,7 +53,8 @@ from endpoints.api.superuser import (SuperUserLogs, SuperUserList, SuperUserMana
|
|||
SuperUserServiceKey, SuperUserServiceKeyApproval,
|
||||
SuperUserTakeOwnership, SuperUserLicense,
|
||||
SuperUserCustomCertificates,
|
||||
SuperUserCustomCertificate)
|
||||
SuperUserCustomCertificate, SuperUserRepositoryBuildLogs,
|
||||
SuperUserRepositoryBuildResource, SuperUserRepositoryBuildStatus)
|
||||
from endpoints.api.globalmessages import GlobalUserMessage, GlobalUserMessages
|
||||
from endpoints.api.secscan import RepositoryImageSecurity
|
||||
from endpoints.api.manifest import RepositoryManifestLabels, ManageRepositoryManifestLabel
|
||||
|
@ -4332,6 +4333,60 @@ class TestSuperUserMessage(ApiTestCase):
|
|||
self._run_test('DELETE', 204, 'devtable', None)
|
||||
|
||||
|
||||
class TestSuperUserRepositoryBuildLogs(ApiTestCase):
|
||||
def setUp(self):
|
||||
ApiTestCase.setUp(self)
|
||||
self._set_url(SuperUserRepositoryBuildLogs, build_uuid='1234')
|
||||
|
||||
def test_get_anonymous(self):
|
||||
self._run_test('GET', 401, None, None)
|
||||
|
||||
def test_get_freshuser(self):
|
||||
self._run_test('GET', 403, 'freshuser', None)
|
||||
|
||||
def test_get_reader(self):
|
||||
self._run_test('GET', 403, 'reader', None)
|
||||
|
||||
def test_get_devtable(self):
|
||||
self._run_test('GET', 400, 'devtable', None)
|
||||
|
||||
|
||||
class TestSuperUserRepositoryBuildStatus(ApiTestCase):
|
||||
def setUp(self):
|
||||
ApiTestCase.setUp(self)
|
||||
self._set_url(SuperUserRepositoryBuildStatus, build_uuid='1234')
|
||||
|
||||
def test_get_anonymous(self):
|
||||
self._run_test('GET', 401, None, None)
|
||||
|
||||
def test_get_freshuser(self):
|
||||
self._run_test('GET', 403, 'freshuser', None)
|
||||
|
||||
def test_get_reader(self):
|
||||
self._run_test('GET', 403, 'reader', None)
|
||||
|
||||
def test_get_devtable(self):
|
||||
self._run_test('GET', 400, 'devtable', None)
|
||||
|
||||
|
||||
class TestSuperUserRepositoryBuildResource(ApiTestCase):
|
||||
def setUp(self):
|
||||
ApiTestCase.setUp(self)
|
||||
self._set_url(SuperUserRepositoryBuildResource, build_uuid='1234')
|
||||
|
||||
def test_get_anonymous(self):
|
||||
self._run_test('GET', 401, None, None)
|
||||
|
||||
def test_get_freshuser(self):
|
||||
self._run_test('GET', 403, 'freshuser', None)
|
||||
|
||||
def test_get_reader(self):
|
||||
self._run_test('GET', 403, 'reader', None)
|
||||
|
||||
def test_get_devtable(self):
|
||||
self._run_test('GET', 404, 'devtable', None)
|
||||
|
||||
|
||||
class TestUserInvoiceFieldList(ApiTestCase):
|
||||
def setUp(self):
|
||||
ApiTestCase.setUp(self)
|
||||
|
|
Reference in a new issue