Merge pull request #2637 from jzelinskie/audit-apps
Audit Logs for Apps
This commit is contained in:
commit
702cdf59ff
12 changed files with 96 additions and 26 deletions
|
@ -7,10 +7,10 @@ from flask import request, url_for
|
||||||
from flask_principal import identity_changed, Identity
|
from flask_principal import identity_changed, Identity
|
||||||
|
|
||||||
from app import app, get_app_url, instance_keys
|
from app import app, get_app_url, instance_keys
|
||||||
from .auth_context import set_grant_context, get_grant_context
|
from auth.auth_context import (set_grant_context, get_grant_context)
|
||||||
from .permissions import repository_read_grant, repository_write_grant, repository_admin_grant
|
from auth.permissions import repository_read_grant, repository_write_grant, repository_admin_grant
|
||||||
from util.names import parse_namespace_repository
|
|
||||||
from util.http import abort
|
from util.http import abort
|
||||||
|
from util.names import parse_namespace_repository
|
||||||
from util.security.registry_jwt import (ANONYMOUS_SUB, decode_bearer_header,
|
from util.security.registry_jwt import (ANONYMOUS_SUB, decode_bearer_header,
|
||||||
InvalidBearerTokenException)
|
InvalidBearerTokenException)
|
||||||
from data import model
|
from data import model
|
||||||
|
@ -18,8 +18,10 @@ from data import model
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
CONTEXT_KINDS = ['user', 'token', 'oauth']
|
CONTEXT_KINDS = ['user', 'token', 'oauth']
|
||||||
|
|
||||||
|
|
||||||
ACCESS_SCHEMA = {
|
ACCESS_SCHEMA = {
|
||||||
'type': 'array',
|
'type': 'array',
|
||||||
'description': 'List of access granted to the subject',
|
'description': 'List of access granted to the subject',
|
||||||
|
|
|
@ -10,8 +10,11 @@ from six import add_metaclass
|
||||||
from app import storage, authentication
|
from app import storage, authentication
|
||||||
from data import model, oci_model
|
from data import model, oci_model
|
||||||
from data.database import Tag, Manifest, MediaType, Blob, Repository, Channel
|
from data.database import Tag, Manifest, MediaType, Blob, Repository, Channel
|
||||||
|
from util.audit import track_and_log
|
||||||
|
from util.morecollections import AttrDict
|
||||||
from util.names import parse_robot_username
|
from util.names import parse_robot_username
|
||||||
|
|
||||||
|
|
||||||
class BlobDescriptor(namedtuple('Blob', ['mediaType', 'size', 'digest', 'urls'])):
|
class BlobDescriptor(namedtuple('Blob', ['mediaType', 'size', 'digest', 'urls'])):
|
||||||
""" BlobDescriptor describes a blob with its mediatype, size and digest.
|
""" BlobDescriptor describes a blob with its mediatype, size and digest.
|
||||||
A BlobDescriptor is used to retrieves the actual blob.
|
A BlobDescriptor is used to retrieves the actual blob.
|
||||||
|
@ -55,10 +58,6 @@ class AppRegistryDataInterface(object):
|
||||||
""" Interface that represents all data store interactions required by a App Registry.
|
""" Interface that represents all data store interactions required by a App Registry.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _application(self, package_name):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def list_applications(self, namespace=None, media_type=None, search=None, username=None,
|
def list_applications(self, namespace=None, media_type=None, search=None, username=None,
|
||||||
with_channels=False):
|
with_channels=False):
|
||||||
|
@ -175,6 +174,11 @@ class AppRegistryDataInterface(object):
|
||||||
Raises: ChannelNotFound, PackageNotFound
|
Raises: ChannelNotFound, PackageNotFound
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def log_action(self, event_name, namespace_name, repo_name=None, analytics_name=None,
|
||||||
|
analytics_sample=1, **kwargs):
|
||||||
|
""" Logs an action to the audit log. """
|
||||||
|
|
||||||
|
|
||||||
def _split_package_name(package):
|
def _split_package_name(package):
|
||||||
""" Returns the namespace and package-name """
|
""" Returns the namespace and package-name """
|
||||||
|
@ -200,6 +204,22 @@ class OCIAppModel(AppRegistryDataInterface):
|
||||||
raise_package_not_found(package)
|
raise_package_not_found(package)
|
||||||
return repo
|
return repo
|
||||||
|
|
||||||
|
def log_action(self, event_name, namespace_name, repo_name=None, analytics_name=None,
|
||||||
|
analytics_sample=1, metadata=None):
|
||||||
|
metadata = {} if metadata is None else metadata
|
||||||
|
|
||||||
|
repo = None
|
||||||
|
if repo_name is not None:
|
||||||
|
db_repo = model.repository.get_repository(namespace_name, repo_name,
|
||||||
|
kind_filter='application')
|
||||||
|
repo = AttrDict({
|
||||||
|
'id': db_repo.id,
|
||||||
|
'name': db_repo.name,
|
||||||
|
'namespace_name': db_repo.namespace_user.username,
|
||||||
|
})
|
||||||
|
track_and_log(event_name, repo, analytics_name=analytics_name,
|
||||||
|
analytics_sample=analytics_sample, **metadata)
|
||||||
|
|
||||||
def list_applications(self, namespace=None, media_type=None, search=None, username=None,
|
def list_applications(self, namespace=None, media_type=None, search=None, username=None,
|
||||||
with_channels=False):
|
with_channels=False):
|
||||||
""" Lists all repositories that contain applications, with optional filtering to a specific
|
""" Lists all repositories that contain applications, with optional filtering to a specific
|
||||||
|
@ -248,7 +268,7 @@ class OCIAppModel(AppRegistryDataInterface):
|
||||||
def create_application(self, package_name, visibility, owner):
|
def create_application(self, package_name, visibility, owner):
|
||||||
""" Create a new app repository, owner is the user who creates it """
|
""" Create a new app repository, owner is the user who creates it """
|
||||||
ns, name = _split_package_name(package_name)
|
ns, name = _split_package_name(package_name)
|
||||||
model.repository.create_repository(ns, name, owner, visibility, "application")
|
model.repository.create_repository(ns, name, owner, visibility, 'application')
|
||||||
|
|
||||||
def application_exists(self, package_name):
|
def application_exists(self, package_name):
|
||||||
""" Create a new app repository, owner is the user who creates it """
|
""" Create a new app repository, owner is the user who creates it """
|
||||||
|
|
|
@ -13,6 +13,7 @@ from flask import jsonify, request
|
||||||
from auth.auth_context import get_authenticated_user
|
from auth.auth_context import get_authenticated_user
|
||||||
from auth.decorators import process_auth
|
from auth.decorators import process_auth
|
||||||
from auth.permissions import (CreateRepositoryPermission, ModifyRepositoryPermission)
|
from auth.permissions import (CreateRepositoryPermission, ModifyRepositoryPermission)
|
||||||
|
from data.interfaces.appr import oci_app_model as model
|
||||||
from endpoints.appr import (appr_bp, require_app_repo_read, require_app_repo_write)
|
from endpoints.appr import (appr_bp, require_app_repo_read, require_app_repo_write)
|
||||||
from endpoints.appr.cnr_backend import Blob, Channel, Package, User
|
from endpoints.appr.cnr_backend import Blob, Channel, Package, User
|
||||||
from endpoints.appr.decorators import disallow_for_image_repository
|
from endpoints.appr.decorators import disallow_for_image_repository
|
||||||
|
@ -102,6 +103,8 @@ def list_packages():
|
||||||
def delete_package(namespace, package_name, release, media_type):
|
def delete_package(namespace, package_name, release, media_type):
|
||||||
reponame = repo_name(namespace, package_name)
|
reponame = repo_name(namespace, package_name)
|
||||||
result = cnr_registry.delete_package(reponame, release, media_type, package_class=Package)
|
result = cnr_registry.delete_package(reponame, release, media_type, package_class=Package)
|
||||||
|
model.log_action('delete_tag', namespace, repo_name=package_name,
|
||||||
|
metadata={'release': release, 'mediatype': media_type})
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
@ -136,7 +139,7 @@ def show_package_releases(namespace, package_name):
|
||||||
@process_auth
|
@process_auth
|
||||||
@require_app_repo_read
|
@require_app_repo_read
|
||||||
@anon_protect
|
@anon_protect
|
||||||
def show_package_releasse_manifests(namespace, package_name, release):
|
def show_package_release_manifests(namespace, package_name, release):
|
||||||
reponame = repo_name(namespace, package_name)
|
reponame = repo_name(namespace, package_name)
|
||||||
result = cnr_registry.show_package_manifests(reponame, release, package_class=Package)
|
result = cnr_registry.show_package_manifests(reponame, release, package_class=Package)
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
@ -153,6 +156,8 @@ def pull(namespace, package_name, release, media_type):
|
||||||
reponame = repo_name(namespace, package_name)
|
reponame = repo_name(namespace, package_name)
|
||||||
logger.info("pull %s", reponame)
|
logger.info("pull %s", reponame)
|
||||||
data = cnr_registry.pull(reponame, release, media_type, Package, blob_class=Blob)
|
data = cnr_registry.pull(reponame, release, media_type, Package, blob_class=Blob)
|
||||||
|
model.log_action('pull_repo', namespace, repo_name=package_name,
|
||||||
|
metadata={'release': release, 'mediatype': media_type})
|
||||||
return _pull(data)
|
return _pull(data)
|
||||||
|
|
||||||
|
|
||||||
|
@ -178,6 +183,7 @@ def push(namespace, package_name):
|
||||||
{"package": reponame,
|
{"package": reponame,
|
||||||
"scopes": ['create']})
|
"scopes": ['create']})
|
||||||
Package.create_repository(reponame, private, owner)
|
Package.create_repository(reponame, private, owner)
|
||||||
|
model.log_action('create_repo', namespace, repo_name=package_name)
|
||||||
|
|
||||||
if not ModifyRepositoryPermission(namespace, package_name).can():
|
if not ModifyRepositoryPermission(namespace, package_name).can():
|
||||||
raise Forbidden("Unauthorized access for: %s" % reponame,
|
raise Forbidden("Unauthorized access for: %s" % reponame,
|
||||||
|
@ -194,6 +200,8 @@ def push(namespace, package_name):
|
||||||
blob = Blob(reponame, values['blob'])
|
blob = Blob(reponame, values['blob'])
|
||||||
app_release = cnr_registry.push(reponame, release_version, media_type, blob, force,
|
app_release = cnr_registry.push(reponame, release_version, media_type, blob, force,
|
||||||
package_class=Package, user=owner, visibility=private)
|
package_class=Package, user=owner, visibility=private)
|
||||||
|
model.log_action('push_repo', namespace, repo_name=package_name,
|
||||||
|
metadata={'release': release_version})
|
||||||
return jsonify(app_release)
|
return jsonify(app_release)
|
||||||
|
|
||||||
|
|
||||||
|
@ -246,6 +254,8 @@ def add_channel_release(namespace, package_name, channel_name, release):
|
||||||
reponame = repo_name(namespace, package_name)
|
reponame = repo_name(namespace, package_name)
|
||||||
result = cnr_registry.add_channel_release(reponame, channel_name, release, channel_class=Channel,
|
result = cnr_registry.add_channel_release(reponame, channel_name, release, channel_class=Channel,
|
||||||
package_class=Package)
|
package_class=Package)
|
||||||
|
model.log_action('create_tag', namespace, repo_name=package_name,
|
||||||
|
metadata={'channel': channel_name, 'release': release})
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
@ -254,13 +264,13 @@ def _check_channel_name(channel_name, release=None):
|
||||||
logger.debug('Found invalid channel name CNR add channel release: %s', channel_name)
|
logger.debug('Found invalid channel name CNR add channel release: %s', channel_name)
|
||||||
raise InvalidUsage("Found invalid channelname %s" % release,
|
raise InvalidUsage("Found invalid channelname %s" % release,
|
||||||
{'name': channel_name,
|
{'name': channel_name,
|
||||||
"release": release})
|
'release': release})
|
||||||
|
|
||||||
if release is not None and not TAG_REGEX.match(release):
|
if release is not None and not TAG_REGEX.match(release):
|
||||||
logger.debug('Found invalid release name CNR add channel release: %s', release)
|
logger.debug('Found invalid release name CNR add channel release: %s', release)
|
||||||
raise InvalidUsage("Found invalid channel release name %s" % release,
|
raise InvalidUsage('Found invalid channel release name %s' % release,
|
||||||
{'name': channel_name,
|
{'name': channel_name,
|
||||||
"release": release})
|
'release': release})
|
||||||
|
|
||||||
|
|
||||||
@appr_bp.route(
|
@appr_bp.route(
|
||||||
|
@ -275,6 +285,8 @@ def delete_channel_release(namespace, package_name, channel_name, release):
|
||||||
reponame = repo_name(namespace, package_name)
|
reponame = repo_name(namespace, package_name)
|
||||||
result = cnr_registry.delete_channel_release(reponame, channel_name, release,
|
result = cnr_registry.delete_channel_release(reponame, channel_name, release,
|
||||||
channel_class=Channel, package_class=Package)
|
channel_class=Channel, package_class=Package)
|
||||||
|
model.log_action('delete_tag', namespace, repo_name=package_name,
|
||||||
|
metadata={'channel': channel_name, 'release': release})
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
@ -289,4 +301,6 @@ def delete_channel(namespace, package_name, channel_name):
|
||||||
_check_channel_name(channel_name)
|
_check_channel_name(channel_name)
|
||||||
reponame = repo_name(namespace, package_name)
|
reponame = repo_name(namespace, package_name)
|
||||||
result = cnr_registry.delete_channel(reponame, channel_name, channel_class=Channel)
|
result = cnr_registry.delete_channel(reponame, channel_name, channel_class=Channel)
|
||||||
|
model.log_action('delete_tag', namespace, repo_name=package_name,
|
||||||
|
metadata={'channel': channel_name})
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
|
@ -6,7 +6,6 @@ from functools import wraps
|
||||||
|
|
||||||
from flask import request, make_response, jsonify, session
|
from flask import request, make_response, jsonify, session
|
||||||
|
|
||||||
from data.interfaces.v1 import pre_oci_model as model
|
|
||||||
from app import authentication, userevents, metric_queue
|
from app import authentication, userevents, metric_queue
|
||||||
from auth.auth_context import get_authenticated_user, get_validated_token, get_validated_oauth_token
|
from auth.auth_context import get_authenticated_user, get_validated_token, get_validated_oauth_token
|
||||||
from auth.decorators import process_auth
|
from auth.decorators import process_auth
|
||||||
|
@ -14,13 +13,14 @@ from auth.permissions import (ModifyRepositoryPermission, UserAdminPermission,
|
||||||
ReadRepositoryPermission, CreateRepositoryPermission,
|
ReadRepositoryPermission, CreateRepositoryPermission,
|
||||||
repository_read_grant, repository_write_grant)
|
repository_read_grant, repository_write_grant)
|
||||||
from auth.signedgrant import generate_signed_token
|
from auth.signedgrant import generate_signed_token
|
||||||
|
from data.interfaces.v1 import pre_oci_model as model
|
||||||
|
from endpoints.common import parse_repository_name
|
||||||
|
from endpoints.decorators import anon_protect, anon_allowed
|
||||||
|
from endpoints.notificationhelper import spawn_notification
|
||||||
|
from endpoints.v1 import v1_bp
|
||||||
|
from util.audit import track_and_log
|
||||||
from util.http import abort
|
from util.http import abort
|
||||||
from util.names import REPOSITORY_NAME_REGEX
|
from util.names import REPOSITORY_NAME_REGEX
|
||||||
from endpoints.common import parse_repository_name
|
|
||||||
from endpoints.v1 import v1_bp
|
|
||||||
from endpoints.trackhelper import track_and_log
|
|
||||||
from endpoints.notificationhelper import spawn_notification
|
|
||||||
from endpoints.decorators import anon_protect, anon_allowed
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
@ -4,7 +4,6 @@ import json
|
||||||
from flask import abort, request, jsonify, make_response, session
|
from flask import abort, request, jsonify, make_response, session
|
||||||
|
|
||||||
|
|
||||||
from util.names import TAG_ERROR, TAG_REGEX
|
|
||||||
from auth.decorators import process_auth
|
from auth.decorators import process_auth
|
||||||
from auth.permissions import (ReadRepositoryPermission,
|
from auth.permissions import (ReadRepositoryPermission,
|
||||||
ModifyRepositoryPermission)
|
ModifyRepositoryPermission)
|
||||||
|
@ -13,7 +12,8 @@ from data.interfaces.v1 import pre_oci_model as model
|
||||||
from endpoints.common import parse_repository_name
|
from endpoints.common import parse_repository_name
|
||||||
from endpoints.decorators import anon_protect
|
from endpoints.decorators import anon_protect
|
||||||
from endpoints.v1 import v1_bp
|
from endpoints.v1 import v1_bp
|
||||||
from endpoints.trackhelper import track_and_log
|
from util.audit import track_and_log
|
||||||
|
from util.names import TAG_ERROR, TAG_REGEX
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
@ -15,11 +15,11 @@ from endpoints.decorators import anon_protect
|
||||||
from endpoints.v2 import v2_bp, require_repo_read, require_repo_write
|
from endpoints.v2 import v2_bp, require_repo_read, require_repo_write
|
||||||
from endpoints.v2.errors import (BlobUnknown, ManifestInvalid, ManifestUnknown, TagInvalid,
|
from endpoints.v2.errors import (BlobUnknown, ManifestInvalid, ManifestUnknown, TagInvalid,
|
||||||
NameInvalid)
|
NameInvalid)
|
||||||
from endpoints.trackhelper import track_and_log
|
|
||||||
from endpoints.notificationhelper import spawn_notification
|
from endpoints.notificationhelper import spawn_notification
|
||||||
from image.docker import ManifestException
|
from image.docker import ManifestException
|
||||||
from image.docker.schema1 import DockerSchema1Manifest, DockerSchema1ManifestBuilder
|
from image.docker.schema1 import DockerSchema1Manifest, DockerSchema1ManifestBuilder
|
||||||
from image.docker.schema2 import DOCKER_SCHEMA2_CONTENT_TYPES
|
from image.docker.schema2 import DOCKER_SCHEMA2_CONTENT_TYPES
|
||||||
|
from util.audit import track_and_log
|
||||||
from util.names import VALID_TAG_PATTERN
|
from util.names import VALID_TAG_PATTERN
|
||||||
from util.registry.replication import queue_replication_batch
|
from util.registry.replication import queue_replication_batch
|
||||||
from util.validation import is_json
|
from util.validation import is_json
|
||||||
|
|
|
@ -13,11 +13,11 @@ from data import database
|
||||||
from data.interfaces.verbs import pre_oci_model as model
|
from data.interfaces.verbs import pre_oci_model as model
|
||||||
from endpoints.common import route_show_if, parse_repository_name
|
from endpoints.common import route_show_if, parse_repository_name
|
||||||
from endpoints.decorators import anon_protect
|
from endpoints.decorators import anon_protect
|
||||||
from endpoints.trackhelper import track_and_log
|
|
||||||
from endpoints.v2.blob import BLOB_DIGEST_ROUTE
|
from endpoints.v2.blob import BLOB_DIGEST_ROUTE
|
||||||
from image.appc import AppCImageFormatter
|
from image.appc import AppCImageFormatter
|
||||||
from image.docker.squashed import SquashedDockerImageFormatter
|
from image.docker.squashed import SquashedDockerImageFormatter
|
||||||
from storage import Storage
|
from storage import Storage
|
||||||
|
from util.audit import track_and_log
|
||||||
from util.http import exact_abort
|
from util.http import exact_abort
|
||||||
from util.registry.filelike import wrap_with_handler
|
from util.registry.filelike import wrap_with_handler
|
||||||
from util.registry.queuefile import QueueFile
|
from util.registry.queuefile import QueueFile
|
||||||
|
|
|
@ -21,8 +21,10 @@
|
||||||
<cor-tab tab-title="Releases" tab-id="releases">
|
<cor-tab tab-title="Releases" tab-id="releases">
|
||||||
<i class="fa ci-package"></i>
|
<i class="fa ci-package"></i>
|
||||||
</cor-tab>
|
</cor-tab>
|
||||||
<cor-tab tab-title="Settings" tab-id="settings"
|
<cor-tab tab-title="Usage Logs" tab-id="logs" tab-init="$ctrl.showLogs()" ng-if="$ctrl.repository.can_admin">
|
||||||
ng-if="$ctrl.repository.can_admin">
|
<i class="fa fa-bar-chart"></i>
|
||||||
|
</cor-tab>
|
||||||
|
<cor-tab tab-title="Settings" tab-id="settings" tab-init="$ctrl.showSettings()" ng-if="$ctrl.repository.can_admin">
|
||||||
<i class="fa fa-gear"></i>
|
<i class="fa fa-gear"></i>
|
||||||
</cor-tab>
|
</cor-tab>
|
||||||
</cor-tabs>
|
</cor-tabs>
|
||||||
|
@ -85,6 +87,12 @@
|
||||||
</div>
|
</div>
|
||||||
</cor-tab-pane>
|
</cor-tab-pane>
|
||||||
|
|
||||||
|
<!-- Usage Logs-->
|
||||||
|
<cor-tab-pane id="logs" ng-if="$ctrl.repository.can_admin">
|
||||||
|
<div class="logs-view" repository="$ctrl.repository" makevisible="$ctrl.logsShown"></div>
|
||||||
|
</cor-tab-pane>
|
||||||
|
</cor-tab-content>
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<cor-tab-pane id="settings" ng-if="$ctrl.repository.can_admin">
|
<cor-tab-pane id="settings" ng-if="$ctrl.repository.can_admin">
|
||||||
<div class="repo-panel-settings" repository="$ctrl.repository" is-enabled="$ctrl.settingsShown"></div>
|
<div class="repo-panel-settings" repository="$ctrl.repository" is-enabled="$ctrl.settingsShown"></div>
|
||||||
|
@ -123,4 +131,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { Input, Component, Inject } from 'ng-metadata/core';
|
||||||
export class AppPublicViewComponent {
|
export class AppPublicViewComponent {
|
||||||
@Input('<') public repository: any;
|
@Input('<') public repository: any;
|
||||||
private settingsShown: number = 0;
|
private settingsShown: number = 0;
|
||||||
|
private logsShown: number = 0;
|
||||||
|
|
||||||
constructor(@Inject('Config') private Config: any) {
|
constructor(@Inject('Config') private Config: any) {
|
||||||
this.updateDescription = this.updateDescription.bind(this);
|
this.updateDescription = this.updateDescription.bind(this);
|
||||||
|
@ -24,4 +25,8 @@ export class AppPublicViewComponent {
|
||||||
public showSettings(): void {
|
public showSettings(): void {
|
||||||
this.settingsShown++;
|
this.settingsShown++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public showLogs(): void {
|
||||||
|
this.logsShown++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,8 @@ angular.module('quay').directive('logsView', function () {
|
||||||
'push_repo': function(metadata) {
|
'push_repo': function(metadata) {
|
||||||
if (metadata.tag) {
|
if (metadata.tag) {
|
||||||
return 'Push of {tag} to repository {namespace}/{repo}';
|
return 'Push of {tag} to repository {namespace}/{repo}';
|
||||||
|
} else if (metadata.release) {
|
||||||
|
return 'Push of {release} to repository {namespace}/{repo}';
|
||||||
} else {
|
} else {
|
||||||
return 'Repository push to {namespace}/{repo}';
|
return 'Repository push to {namespace}/{repo}';
|
||||||
}
|
}
|
||||||
|
@ -91,6 +93,15 @@ angular.module('quay').directive('logsView', function () {
|
||||||
description = 'tag {tag} from repository {namespace}/{repo}';
|
description = 'tag {tag} from repository {namespace}/{repo}';
|
||||||
} else if (metadata.manifest_digest) {
|
} else if (metadata.manifest_digest) {
|
||||||
description = 'digest {manifest_digest} from repository {namespace}/{repo}';
|
description = 'digest {manifest_digest} from repository {namespace}/{repo}';
|
||||||
|
} else if (metadata.release) {
|
||||||
|
description = 'release {release}';
|
||||||
|
if (metadata.channel) {
|
||||||
|
description += ' via channel {channel}';
|
||||||
|
}
|
||||||
|
if (metadata.mediatype) {
|
||||||
|
description += ' for {mediatype}';
|
||||||
|
}
|
||||||
|
description += ' from repository {namespace}/{repo}';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadata.token) {
|
if (metadata.token) {
|
||||||
|
|
|
@ -1015,6 +1015,8 @@ class RegistryTestsMixin(object):
|
||||||
|
|
||||||
self.assertEquals(1, len(logs))
|
self.assertEquals(1, len(logs))
|
||||||
self.assertEquals('push_repo', logs[0]['kind'])
|
self.assertEquals('push_repo', logs[0]['kind'])
|
||||||
|
self.assertEquals('public', logs[0]['metadata']['namespace'])
|
||||||
|
self.assertEquals('newrepo', logs[0]['metadata']['repo'])
|
||||||
self.assertEquals('public', logs[0]['performer']['name'])
|
self.assertEquals('public', logs[0]['performer']['name'])
|
||||||
|
|
||||||
# Pull the repository.
|
# Pull the repository.
|
||||||
|
@ -1044,6 +1046,8 @@ class RegistryTestsMixin(object):
|
||||||
|
|
||||||
self.assertEquals(1, len(logs))
|
self.assertEquals(1, len(logs))
|
||||||
self.assertEquals('push_repo', logs[0]['kind'])
|
self.assertEquals('push_repo', logs[0]['kind'])
|
||||||
|
self.assertEquals('buynlarge', logs[0]['metadata']['namespace'])
|
||||||
|
self.assertEquals('newrepo', logs[0]['metadata']['repo'])
|
||||||
self.assertEquals('buynlarge+ownerbot', logs[0]['performer']['name'])
|
self.assertEquals('buynlarge+ownerbot', logs[0]['performer']['name'])
|
||||||
|
|
||||||
# Pull the repository.
|
# Pull the repository.
|
||||||
|
@ -1055,6 +1059,8 @@ class RegistryTestsMixin(object):
|
||||||
|
|
||||||
self.assertEquals(2, len(logs))
|
self.assertEquals(2, len(logs))
|
||||||
self.assertEquals('pull_repo', logs[0]['kind'])
|
self.assertEquals('pull_repo', logs[0]['kind'])
|
||||||
|
self.assertEquals('buynlarge', logs[0]['metadata']['namespace'])
|
||||||
|
self.assertEquals('newrepo', logs[0]['metadata']['repo'])
|
||||||
self.assertEquals('buynlarge+ownerbot', logs[0]['performer']['name'])
|
self.assertEquals('buynlarge+ownerbot', logs[0]['performer']['name'])
|
||||||
|
|
||||||
|
|
||||||
|
@ -1074,6 +1080,8 @@ class RegistryTestsMixin(object):
|
||||||
logs = result.json()['logs']
|
logs = result.json()['logs']
|
||||||
|
|
||||||
self.assertEquals('pull_repo', logs[0]['kind'])
|
self.assertEquals('pull_repo', logs[0]['kind'])
|
||||||
|
self.assertEquals('devtable', logs[0]['metadata']['namespace'])
|
||||||
|
self.assertEquals('newrepo', logs[0]['metadata']['repo'])
|
||||||
self.assertEquals('my-new-token', logs[0]['metadata']['token'])
|
self.assertEquals('my-new-token', logs[0]['metadata']['token'])
|
||||||
|
|
||||||
|
|
||||||
|
@ -1091,6 +1099,8 @@ class RegistryTestsMixin(object):
|
||||||
|
|
||||||
self.assertEquals(2, len(logs))
|
self.assertEquals(2, len(logs))
|
||||||
self.assertEquals('pull_repo', logs[0]['kind'])
|
self.assertEquals('pull_repo', logs[0]['kind'])
|
||||||
|
self.assertEquals('devtable', logs[0]['metadata']['namespace'])
|
||||||
|
self.assertEquals('newrepo', logs[0]['metadata']['repo'])
|
||||||
|
|
||||||
self.assertEquals('devtable', logs[0]['performer']['name'])
|
self.assertEquals('devtable', logs[0]['performer']['name'])
|
||||||
self.assertEquals(1, logs[0]['metadata']['oauth_token_id'])
|
self.assertEquals(1, logs[0]['metadata']['oauth_token_id'])
|
||||||
|
|
|
@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def track_and_log(event_name, repo_obj, analytics_name=None, analytics_sample=1, **kwargs):
|
def track_and_log(event_name, repo_obj, analytics_name=None, analytics_sample=1, **kwargs):
|
||||||
repo_name = repo_obj.name
|
repo_name = repo_obj.name
|
||||||
namespace_name = repo_obj.namespace_name,
|
namespace_name = repo_obj.namespace_name
|
||||||
metadata = {
|
metadata = {
|
||||||
'repo': repo_name,
|
'repo': repo_name,
|
||||||
'namespace': namespace_name,
|
'namespace': namespace_name,
|
Reference in a new issue