Put aggregated log query and log exports behind feature flags
This commit is contained in:
parent
4ba4d9141b
commit
204eb74c4f
7 changed files with 60 additions and 20 deletions
|
@ -553,3 +553,9 @@ class DefaultConfig(ImmutableConfig):
|
||||||
|
|
||||||
# Feature Flag: Whether to record when users were last accessed.
|
# Feature Flag: Whether to record when users were last accessed.
|
||||||
FEATURE_USER_LAST_ACCESSED = True
|
FEATURE_USER_LAST_ACCESSED = True
|
||||||
|
|
||||||
|
# Feature Flag: Whether to allow users to retrieve aggregated log counts.
|
||||||
|
FEATURE_AGGREGATED_LOG_COUNT_RETRIEVAL = True
|
||||||
|
|
||||||
|
# Feature Flag: Whether to support log exporting.
|
||||||
|
FEATURE_LOG_EXPORT = True
|
||||||
|
|
|
@ -6,11 +6,13 @@ from datetime import datetime, timedelta
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
|
import features
|
||||||
|
|
||||||
from app import export_action_logs_queue
|
from app import export_action_logs_queue
|
||||||
from endpoints.api import (resource, nickname, ApiResource, query_param, parse_args,
|
from endpoints.api import (resource, nickname, ApiResource, query_param, parse_args,
|
||||||
RepositoryParamResource, require_repo_admin, related_user_resource,
|
RepositoryParamResource, require_repo_admin, related_user_resource,
|
||||||
format_date, require_user_admin, path_param, require_scope, page_support,
|
format_date, require_user_admin, path_param, require_scope, page_support,
|
||||||
validate_json_request, InvalidRequest)
|
validate_json_request, InvalidRequest, show_if)
|
||||||
from data import model as data_model
|
from data import model as data_model
|
||||||
from endpoints.api.logs_models_pre_oci import pre_oci_model as model
|
from endpoints.api.logs_models_pre_oci import pre_oci_model as model
|
||||||
from endpoints.exception import Unauthorized, NotFound
|
from endpoints.exception import Unauthorized, NotFound
|
||||||
|
@ -150,6 +152,7 @@ class OrgLogs(ApiResource):
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/repository/<apirepopath:repository>/aggregatelogs')
|
@resource('/v1/repository/<apirepopath:repository>/aggregatelogs')
|
||||||
|
@show_if(features.AGGREGATED_LOG_COUNT_RETRIEVAL)
|
||||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||||
class RepositoryAggregateLogs(RepositoryParamResource):
|
class RepositoryAggregateLogs(RepositoryParamResource):
|
||||||
""" Resource for fetching aggregated logs for the specific repository. """
|
""" Resource for fetching aggregated logs for the specific repository. """
|
||||||
|
@ -170,6 +173,7 @@ class RepositoryAggregateLogs(RepositoryParamResource):
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/user/aggregatelogs')
|
@resource('/v1/user/aggregatelogs')
|
||||||
|
@show_if(features.AGGREGATED_LOG_COUNT_RETRIEVAL)
|
||||||
class UserAggregateLogs(ApiResource):
|
class UserAggregateLogs(ApiResource):
|
||||||
""" Resource for fetching aggregated logs for the current user. """
|
""" Resource for fetching aggregated logs for the current user. """
|
||||||
|
|
||||||
|
@ -191,6 +195,7 @@ class UserAggregateLogs(ApiResource):
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/organization/<orgname>/aggregatelogs')
|
@resource('/v1/organization/<orgname>/aggregatelogs')
|
||||||
|
@show_if(features.AGGREGATED_LOG_COUNT_RETRIEVAL)
|
||||||
@path_param('orgname', 'The name of the organization')
|
@path_param('orgname', 'The name of the organization')
|
||||||
@related_user_resource(UserLogs)
|
@related_user_resource(UserLogs)
|
||||||
class OrgAggregateLogs(ApiResource):
|
class OrgAggregateLogs(ApiResource):
|
||||||
|
@ -314,6 +319,7 @@ class ExportUserLogs(ApiResource):
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/organization/<orgname>/exportlogs')
|
@resource('/v1/organization/<orgname>/exportlogs')
|
||||||
|
@show_if(features.LOG_EXPORT)
|
||||||
@path_param('orgname', 'The name of the organization')
|
@path_param('orgname', 'The name of the organization')
|
||||||
@related_user_resource(ExportUserLogs)
|
@related_user_resource(ExportUserLogs)
|
||||||
class ExportOrgLogs(ApiResource):
|
class ExportOrgLogs(ApiResource):
|
||||||
|
@ -329,7 +335,7 @@ class ExportOrgLogs(ApiResource):
|
||||||
@require_scope(scopes.ORG_ADMIN)
|
@require_scope(scopes.ORG_ADMIN)
|
||||||
@validate_json_request('ExportLogs')
|
@validate_json_request('ExportLogs')
|
||||||
def post(self, orgname, parsed_args):
|
def post(self, orgname, parsed_args):
|
||||||
""" Gets the aggregated logs for the specified organization. """
|
""" Exports the logs for the specified organization. """
|
||||||
permission = AdministerOrganizationPermission(orgname)
|
permission = AdministerOrganizationPermission(orgname)
|
||||||
if permission.can():
|
if permission.can():
|
||||||
start_time = parsed_args['starttime']
|
start_time = parsed_args['starttime']
|
||||||
|
|
|
@ -22,14 +22,16 @@
|
||||||
</span>
|
</span>
|
||||||
<span class="hidden-xs right">
|
<span class="hidden-xs right">
|
||||||
<i class="fa fa-bar-chart-o toggle-icon" ng-class="chartVisible ? 'active' : ''"
|
<i class="fa fa-bar-chart-o toggle-icon" ng-class="chartVisible ? 'active' : ''"
|
||||||
ng-click="toggleChart()" data-title="Toggle Chart" bs-tooltip="tooltip.title"></i>
|
ng-click="toggleChart()" data-title="Toggle Chart" bs-tooltip="tooltip.title"
|
||||||
|
quay-show="Features.AGGREGATED_LOG_COUNT_RETRIEVAL"></i>
|
||||||
<button class="btn btn-default download-btn" ng-click="showExportLogs()"
|
<button class="btn btn-default download-btn" ng-click="showExportLogs()"
|
||||||
ng-if="user || organization || repository"><i class="fa fa-download"></i>Export Logs</button>
|
ng-if="(user || organization || repository) && Features.LOG_EXPORT"><i class="fa fa-download"></i>Export Logs</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div id="bar-chart" style="width: 800px; height: 500px;" ng-show="chartVisible">
|
<div id="bar-chart" style="width: 800px; height: 500px;"
|
||||||
|
quay-show="chartVisible && Features.AGGREGATED_LOG_COUNT_RETRIEVAL">
|
||||||
<svg style="width: 800px; height: 500px;"></svg>
|
<svg style="width: 800px; height: 500px;"></svg>
|
||||||
<div class="cor-loader" ng-if="chartLoading"></div>
|
<div class="cor-loader" ng-if="chartLoading"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,7 +19,9 @@ angular.module('quay').directive('logsView', function () {
|
||||||
'allLogs': '@allLogs'
|
'allLogs': '@allLogs'
|
||||||
},
|
},
|
||||||
controller: function($scope, $element, $sce, Restangular, ApiService, TriggerService,
|
controller: function($scope, $element, $sce, Restangular, ApiService, TriggerService,
|
||||||
StringBuilderService, ExternalNotificationData, UtilService) {
|
StringBuilderService, ExternalNotificationData, UtilService,
|
||||||
|
Features) {
|
||||||
|
$scope.Features = Features;
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
$scope.loadCounter = -1;
|
$scope.loadCounter = -1;
|
||||||
$scope.logs = null;
|
$scope.logs = null;
|
||||||
|
@ -405,20 +407,22 @@ angular.module('quay').directive('logsView', function () {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.chartLoading = true;
|
if (Features.AGGREGATED_LOG_COUNT_RETRIEVAL) {
|
||||||
|
$scope.chartLoading = true;
|
||||||
|
|
||||||
var aggregateUrl = getUrl('aggregatelogs').toString();
|
var aggregateUrl = getUrl('aggregatelogs').toString();
|
||||||
var loadAggregate = Restangular.one(aggregateUrl);
|
var loadAggregate = Restangular.one(aggregateUrl);
|
||||||
loadAggregate.customGET().then(function(resp) {
|
loadAggregate.customGET().then(function(resp) {
|
||||||
$scope.chart = new LogUsageChart(logKinds);
|
$scope.chart = new LogUsageChart(logKinds);
|
||||||
$($scope.chart).bind('filteringChanged', function(e) {
|
$($scope.chart).bind('filteringChanged', function(e) {
|
||||||
$scope.$apply(function() { $scope.kindsAllowed = e.allowed; });
|
$scope.$apply(function() { $scope.kindsAllowed = e.allowed; });
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.chart.draw('bar-chart', resp.aggregated, $scope.options.logStartDate,
|
||||||
|
$scope.options.logEndDate);
|
||||||
|
$scope.chartLoading = false;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
$scope.chart.draw('bar-chart', resp.aggregated, $scope.options.logStartDate,
|
|
||||||
$scope.options.logEndDate);
|
|
||||||
$scope.chartLoading = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.nextPageToken = null;
|
$scope.nextPageToken = null;
|
||||||
$scope.hasAdditional = true;
|
$scope.hasAdditional = true;
|
||||||
|
|
|
@ -784,6 +784,20 @@ CONFIG_SCHEMA = {
|
||||||
'pattern': '^[0-9]+(w|m|d|h|s)$',
|
'pattern': '^[0-9]+(w|m|d|h|s)$',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
# Feature Flag: Aggregated log retrieval.
|
||||||
|
'FEATURE_AGGREGATED_LOG_COUNT_RETRIEVAL': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'Whether to allow retrieval of aggregated log counts. Defaults to True',
|
||||||
|
'x-example': True,
|
||||||
|
},
|
||||||
|
|
||||||
|
# Feature Flag: Log export.
|
||||||
|
'FEATURE_LOG_EXPORT': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'Whether to allow exporting of action logs. Defaults to True',
|
||||||
|
'x-example': True,
|
||||||
|
},
|
||||||
|
|
||||||
# Feature Flag: User last accessed.
|
# Feature Flag: User last accessed.
|
||||||
'FEATURE_USER_LAST_ACCESSED': {
|
'FEATURE_USER_LAST_ACCESSED': {
|
||||||
'type': 'boolean',
|
'type': 'boolean',
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
@ -8,6 +9,8 @@ from io import BytesIO
|
||||||
|
|
||||||
from enum import Enum, unique
|
from enum import Enum, unique
|
||||||
|
|
||||||
|
import features
|
||||||
|
|
||||||
from app import app, export_action_logs_queue, storage, get_app_url
|
from app import app, export_action_logs_queue, storage, get_app_url
|
||||||
from data import model
|
from data import model
|
||||||
from endpoints.api import format_date
|
from endpoints.api import format_date
|
||||||
|
@ -277,6 +280,11 @@ def _run_and_time(fn):
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False)
|
logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False)
|
||||||
|
|
||||||
|
if not features.LOG_EXPORT:
|
||||||
|
logger.debug('Log export not enabled; skipping')
|
||||||
|
while True:
|
||||||
|
time.sleep(100000)
|
||||||
|
|
||||||
logger.debug('Starting export action logs worker')
|
logger.debug('Starting export action logs worker')
|
||||||
worker = ExportActionLogsWorker(export_action_logs_queue,
|
worker = ExportActionLogsWorker(export_action_logs_queue,
|
||||||
poll_period_seconds=POLL_PERIOD_SECONDS)
|
poll_period_seconds=POLL_PERIOD_SECONDS)
|
||||||
|
|
|
@ -129,8 +129,8 @@ def log_dict(log):
|
||||||
def main():
|
def main():
|
||||||
logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False)
|
logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False)
|
||||||
|
|
||||||
if not features.ACTION_LOG_ROTATION or None in [SAVE_PATH, SAVE_LOCATION]:
|
if not features.LOG_EXPORT:
|
||||||
logger.debug('Action log rotation worker not enabled; skipping')
|
logger.debug('Log export not enabled; skipping')
|
||||||
while True:
|
while True:
|
||||||
time.sleep(100000)
|
time.sleep(100000)
|
||||||
|
|
||||||
|
|
Reference in a new issue