Merge pull request #2687 from ecordell/enable-builds-trust

Re-enable builds and tag modification when signing is enabled
This commit is contained in:
Evan Cordell 2017-06-13 11:20:04 -04:00 committed by GitHub
commit b6d423a50d
15 changed files with 39 additions and 118 deletions

View file

@ -387,23 +387,6 @@ def define_json_response(schema_name):
return wrapper return wrapper
def disallow_under_trust(func):
""" Disallows the decorated operation for repository when it has trust enabled.
"""
@wraps(func)
def wrapper(self, *args, **kwargs):
if features.SIGNING:
namespace = args[0]
repository = args[1]
repo = model.repository.get_repository(namespace, repository)
if repo is not None and repo.trust_enabled:
raise InvalidRequest('Cannot call this method on a repostory with trust enabled')
return func(self, *args, **kwargs)
return wrapper
import endpoints.api.billing import endpoints.api.billing
import endpoints.api.build import endpoints.api.build
import endpoints.api.discovery import endpoints.api.discovery

View file

@ -19,8 +19,7 @@ from data.buildlogs import BuildStatusRetrievalError
from endpoints.api import (RepositoryParamResource, parse_args, query_param, nickname, resource, from endpoints.api import (RepositoryParamResource, parse_args, query_param, nickname, resource,
require_repo_read, require_repo_write, validate_json_request, require_repo_read, require_repo_write, validate_json_request,
ApiResource, internal_only, format_date, api, path_param, ApiResource, internal_only, format_date, api, path_param,
require_repo_admin, abort, disallow_for_app_repositories, require_repo_admin, abort, disallow_for_app_repositories)
disallow_under_trust)
from endpoints.building import start_build, PreparedBuild, MaximumBuildsQueuedException from endpoints.building import start_build, PreparedBuild, MaximumBuildsQueuedException
from endpoints.exception import Unauthorized, NotFound, InvalidRequest from endpoints.exception import Unauthorized, NotFound, InvalidRequest
from util.names import parse_robot_username from util.names import parse_robot_username
@ -226,7 +225,6 @@ class RepositoryBuildList(RepositoryParamResource):
@require_repo_write @require_repo_write
@nickname('requestRepoBuild') @nickname('requestRepoBuild')
@disallow_for_app_repositories @disallow_for_app_repositories
@disallow_under_trust
@validate_json_request('RepositoryBuildRequest') @validate_json_request('RepositoryBuildRequest')
def post(self, namespace, repository): def post(self, namespace, repository):
""" Request that a repository be built and pushed from the specified input. """ """ Request that a repository be built and pushed from the specified input. """
@ -363,7 +361,6 @@ class RepositoryBuildResource(RepositoryParamResource):
@require_repo_admin @require_repo_admin
@nickname('cancelRepoBuild') @nickname('cancelRepoBuild')
@disallow_under_trust
@disallow_for_app_repositories @disallow_for_app_repositories
def delete(self, namespace, repository, build_uuid): def delete(self, namespace, repository, build_uuid):
""" Cancels a repository build. """ """ Cancels a repository build. """

View file

@ -5,7 +5,7 @@ from flask import request, abort
from endpoints.api import ( from endpoints.api import (
resource, nickname, require_repo_read, require_repo_write, RepositoryParamResource, log_action, resource, nickname, require_repo_read, require_repo_write, RepositoryParamResource, log_action,
validate_json_request, path_param, parse_args, query_param, truthy_bool, validate_json_request, path_param, parse_args, query_param, truthy_bool,
disallow_for_app_repositories, disallow_under_trust) disallow_for_app_repositories)
from endpoints.exception import NotFound from endpoints.exception import NotFound
from endpoints.api.image import image_view from endpoints.api.image import image_view
from endpoints.v2.manifest import _generate_and_store_manifest from endpoints.v2.manifest import _generate_and_store_manifest
@ -87,7 +87,6 @@ class RepositoryTag(RepositoryParamResource):
@require_repo_write @require_repo_write
@disallow_for_app_repositories @disallow_for_app_repositories
@disallow_under_trust
@nickname('changeTagImage') @nickname('changeTagImage')
@validate_json_request('MoveTag') @validate_json_request('MoveTag')
def put(self, namespace, repository, tag): def put(self, namespace, repository, tag):
@ -128,7 +127,6 @@ class RepositoryTag(RepositoryParamResource):
@require_repo_write @require_repo_write
@disallow_for_app_repositories @disallow_for_app_repositories
@disallow_under_trust
@nickname('deleteFullTag') @nickname('deleteFullTag')
def delete(self, namespace, repository, tag): def delete(self, namespace, repository, tag):
""" Delete the specified repository tag. """ """ Delete the specified repository tag. """
@ -225,7 +223,6 @@ class RestoreTag(RepositoryParamResource):
@require_repo_write @require_repo_write
@disallow_for_app_repositories @disallow_for_app_repositories
@disallow_under_trust
@nickname('restoreTag') @nickname('restoreTag')
@validate_json_request('RestoreTag') @validate_json_request('RestoreTag')
def post(self, namespace, repository, tag): def post(self, namespace, repository, tag):

View file

@ -1,50 +0,0 @@
import pytest
from data import model
from endpoints.api.build import RepositoryBuildList, RepositoryBuildResource
from endpoints.api.tag import RepositoryTag, RestoreTag
from endpoints.api.trigger import (BuildTrigger, BuildTriggerSubdirs,
BuildTriggerActivate, BuildTriggerAnalyze, ActivateBuildTrigger,
BuildTriggerFieldValues, BuildTriggerSources,
BuildTriggerSourceNamespaces)
from endpoints.api.test.shared import client_with_identity, conduct_api_call
from test.fixtures import *
BUILD_ARGS = {'build_uuid': '1234'}
IMAGE_ARGS = {'imageid': '1234', 'image_id': 1234}
MANIFEST_ARGS = {'manifestref': 'sha256:abcd1234'}
LABEL_ARGS = {'manifestref': 'sha256:abcd1234', 'labelid': '1234'}
NOTIFICATION_ARGS = {'uuid': '1234'}
TAG_ARGS = {'tag': 'foobar'}
TRIGGER_ARGS = {'trigger_uuid': '1234'}
FIELD_ARGS = {'trigger_uuid': '1234', 'field_name': 'foobar'}
@pytest.mark.parametrize('resource, method, params', [
(RepositoryBuildList, 'post', None),
(RepositoryBuildResource, 'delete', BUILD_ARGS),
(RepositoryTag, 'put', TAG_ARGS),
(RepositoryTag, 'delete', TAG_ARGS),
(RestoreTag, 'post', TAG_ARGS),
(BuildTrigger, 'delete', TRIGGER_ARGS),
(BuildTriggerSubdirs, 'post', TRIGGER_ARGS),
(BuildTriggerActivate, 'post', TRIGGER_ARGS),
(BuildTriggerAnalyze, 'post', TRIGGER_ARGS),
(ActivateBuildTrigger, 'post', TRIGGER_ARGS),
(BuildTriggerFieldValues, 'post', FIELD_ARGS),
(BuildTriggerSources, 'post', TRIGGER_ARGS),
(BuildTriggerSourceNamespaces, 'get', TRIGGER_ARGS),
])
def test_disallowed_for_apps(resource, method, params, client):
namespace = 'devtable'
repository = 'somerepo'
devtable = model.user.get_user('devtable')
repo = model.repository.create_repository(namespace, repository, devtable, repo_kind='image')
model.repository.set_trust(repo, True)
params = params or {}
params['repository'] = '%s/%s' % (namespace, repository)
with client_with_identity('devtable', client) as cl:
conduct_api_call(cl, resource, method, params, None, 400)

View file

@ -19,7 +19,7 @@ from data.model.build import update_build_trigger
from endpoints.api import (RepositoryParamResource, nickname, resource, require_repo_admin, from endpoints.api import (RepositoryParamResource, nickname, resource, require_repo_admin,
log_action, request_error, query_param, parse_args, internal_only, log_action, request_error, query_param, parse_args, internal_only,
validate_json_request, api, path_param, abort, validate_json_request, api, path_param, abort,
disallow_for_app_repositories, disallow_under_trust) disallow_for_app_repositories)
from endpoints.api.build import build_status_view, trigger_view, RepositoryBuildStatus from endpoints.api.build import build_status_view, trigger_view, RepositoryBuildStatus
from endpoints.api.trigger_analyzer import TriggerAnalyzer from endpoints.api.trigger_analyzer import TriggerAnalyzer
from endpoints.building import start_build, MaximumBuildsQueuedException from endpoints.building import start_build, MaximumBuildsQueuedException
@ -72,7 +72,6 @@ class BuildTrigger(RepositoryParamResource):
@require_repo_admin @require_repo_admin
@disallow_for_app_repositories @disallow_for_app_repositories
@disallow_under_trust
@nickname('deleteBuildTrigger') @nickname('deleteBuildTrigger')
def delete(self, namespace_name, repo_name, trigger_uuid): def delete(self, namespace_name, repo_name, trigger_uuid):
""" Delete the specified build trigger. """ """ Delete the specified build trigger. """
@ -114,7 +113,6 @@ class BuildTriggerSubdirs(RepositoryParamResource):
@require_repo_admin @require_repo_admin
@disallow_for_app_repositories @disallow_for_app_repositories
@disallow_under_trust
@nickname('listBuildTriggerSubdirs') @nickname('listBuildTriggerSubdirs')
@validate_json_request('BuildTriggerSubdirRequest') @validate_json_request('BuildTriggerSubdirRequest')
def post(self, namespace_name, repo_name, trigger_uuid): def post(self, namespace_name, repo_name, trigger_uuid):
@ -179,7 +177,6 @@ class BuildTriggerActivate(RepositoryParamResource):
@require_repo_admin @require_repo_admin
@disallow_for_app_repositories @disallow_for_app_repositories
@disallow_under_trust
@nickname('activateBuildTrigger') @nickname('activateBuildTrigger')
@validate_json_request('BuildTriggerActivateRequest') @validate_json_request('BuildTriggerActivateRequest')
def post(self, namespace_name, repo_name, trigger_uuid): def post(self, namespace_name, repo_name, trigger_uuid):
@ -276,7 +273,6 @@ class BuildTriggerAnalyze(RepositoryParamResource):
@require_repo_admin @require_repo_admin
@disallow_for_app_repositories @disallow_for_app_repositories
@disallow_under_trust
@nickname('analyzeBuildTrigger') @nickname('analyzeBuildTrigger')
@validate_json_request('BuildTriggerAnalyzeRequest') @validate_json_request('BuildTriggerAnalyzeRequest')
def post(self, namespace_name, repo_name, trigger_uuid): def post(self, namespace_name, repo_name, trigger_uuid):
@ -339,7 +335,6 @@ class ActivateBuildTrigger(RepositoryParamResource):
@require_repo_admin @require_repo_admin
@disallow_for_app_repositories @disallow_for_app_repositories
@disallow_under_trust
@nickname('manuallyStartBuildTrigger') @nickname('manuallyStartBuildTrigger')
@validate_json_request('RunParameters') @validate_json_request('RunParameters')
def post(self, namespace_name, repo_name, trigger_uuid): def post(self, namespace_name, repo_name, trigger_uuid):
@ -401,7 +396,6 @@ class BuildTriggerFieldValues(RepositoryParamResource):
@require_repo_admin @require_repo_admin
@disallow_for_app_repositories @disallow_for_app_repositories
@disallow_under_trust
@nickname('listTriggerFieldValues') @nickname('listTriggerFieldValues')
def post(self, namespace_name, repo_name, trigger_uuid, field_name): def post(self, namespace_name, repo_name, trigger_uuid, field_name):
""" List the field values for a custom run field. """ """ List the field values for a custom run field. """
@ -443,7 +437,6 @@ class BuildTriggerSources(RepositoryParamResource):
@require_repo_admin @require_repo_admin
@disallow_for_app_repositories @disallow_for_app_repositories
@disallow_under_trust
@nickname('listTriggerBuildSources') @nickname('listTriggerBuildSources')
@validate_json_request('BuildTriggerSourcesRequest') @validate_json_request('BuildTriggerSourcesRequest')
def post(self, namespace_name, repo_name, trigger_uuid): def post(self, namespace_name, repo_name, trigger_uuid):
@ -475,7 +468,6 @@ class BuildTriggerSourceNamespaces(RepositoryParamResource):
@require_repo_admin @require_repo_admin
@disallow_for_app_repositories @disallow_for_app_repositories
@disallow_under_trust
@nickname('listTriggerBuildSourceNamespaces') @nickname('listTriggerBuildSourceNamespaces')
def get(self, namespace_name, repo_name, trigger_uuid): def get(self, namespace_name, repo_name, trigger_uuid):
""" List the build sources for the trigger configuration thus far. """ """ List the build sources for the trigger configuration thus far. """

View file

@ -674,8 +674,6 @@ def attach_bitbucket_trigger(namespace_name, repo_name):
abort(404, message=msg) abort(404, message=msg)
elif repo.kind.name != 'image': elif repo.kind.name != 'image':
abort(501) abort(501)
elif repo.trust_enabled:
abort(400)
trigger = model.build.create_build_trigger(repo, BitbucketBuildTrigger.service_name(), None, trigger = model.build.create_build_trigger(repo, BitbucketBuildTrigger.service_name(), None,
current_user.db_user()) current_user.db_user())
@ -711,8 +709,6 @@ def attach_custom_build_trigger(namespace_name, repo_name):
abort(404, message=msg) abort(404, message=msg)
elif repo.kind.name != 'image': elif repo.kind.name != 'image':
abort(501) abort(501)
elif repo.trust_enabled:
abort(400)
trigger = model.build.create_build_trigger(repo, CustomBuildTrigger.service_name(), trigger = model.build.create_build_trigger(repo, CustomBuildTrigger.service_name(),
None, current_user.db_user()) None, current_user.db_user())

View file

@ -89,8 +89,6 @@ def build_trigger_webhook(trigger_uuid, **kwargs):
if trigger.repository.kind.name != 'image': if trigger.repository.kind.name != 'image':
abort(501, 'Build triggers cannot be invoked on application repositories') abort(501, 'Build triggers cannot be invoked on application repositories')
elif trigger.repository.trust_enabled:
abort(400, 'Build triggers cannot be invoked on repositories with trust enabled')
logger.debug('Passing webhook request to handler %s', handler) logger.debug('Passing webhook request to handler %s', handler)
try: try:

View file

@ -89,14 +89,14 @@
<i class="fa ci-robot"></i> New Robot Account <i class="fa ci-robot"></i> New Robot Account
</a> </a>
</li> </li>
<li role="presentation" class="divider" ng-if="currentPageContext.repository && currentPageContext.repository.can_write && !currentPageContext.repository.trust_enabled"></li> <li role="presentation" class="divider" ng-if="currentPageContext.repository && currentPageContext.repository.can_write && !currentPageContext.repository.tag_operations_disabled"></li>
<li role="presentation" class="dropdown-header" <li role="presentation" class="dropdown-header"
ng-if="currentPageContext.repository && currentPageContext.repository.can_write && ng-if="currentPageContext.repository && currentPageContext.repository.can_write &&
!currentPageContext.repository.trust_enabled"> !currentPageContext.repository.tag_operations_disabled">
Repository {{ currentPageContext.repository.namespace }}/{{ currentPageContext.repository.name }} Repository {{ currentPageContext.repository.namespace }}/{{ currentPageContext.repository.name }}
</li> </li>
<li ng-if="currentPageContext.repository && currentPageContext.repository.can_write && <li ng-if="currentPageContext.repository && currentPageContext.repository.can_write &&
!currentPageContext.repository.trust_enabled"> !currentPageContext.repository.tag_operations_disabled">
<a ng-click="startBuild()"> <a ng-click="startBuild()">
<i class="fa fa-tasks"></i> New Dockerfile Build <i class="fa fa-tasks"></i> New Dockerfile Build
</a> </a>

View file

@ -1,13 +1,13 @@
<div class="repo-panel-builds-element"> <div class="repo-panel-builds-element">
<div class="feedback-bar" feedback="feedback"></div> <div class="feedback-bar" feedback="feedback"></div>
<div class="tab-header-controls"> <div class="tab-header-controls">
<button class="btn btn-primary" ng-click="showNewBuildDialog()" ng-if="!repository.trust_enabled"> <button class="btn btn-primary" ng-click="showNewBuildDialog()" ng-if="!repository.tag_operations_disabled">
<i class="fa fa-play"></i> Start New Build <i class="fa fa-play"></i> Start New Build
</button> </button>
</div> </div>
<h3 class="tab-header">Repository Builds</h3> <h3 class="tab-header">Repository Builds</h3>
<div class="co-alert co-alert-info" ng-if="repository.trust_enabled"> <div class="co-alert co-alert-info" ng-if="repository.tag_operations_disabled">
Builds cannot be performed on this repository because Quay Trust is Builds cannot be performed on this repository because Quay Trust is
enabled, which requires that all operations be signed by a user. enabled, which requires that all operations be signed by a user.
</div> </div>
@ -83,7 +83,7 @@
</div> <!-- /Builds --> </div> <!-- /Builds -->
<!-- Build Triggers --> <!-- Build Triggers -->
<div class="co-panel" ng-if="repository.can_admin && TriggerService.getTypes().length && !repository.trust_enabled" id="repoBuildTriggers"> <div class="co-panel" ng-if="repository.can_admin && TriggerService.getTypes().length && !repository.tag_operations_disabled" id="repoBuildTriggers">
<!-- Builds header controls --> <!-- Builds header controls -->
<div class="co-panel-heading"> <div class="co-panel-heading">
<i class="fa fa-flash"></i> <i class="fa fa-flash"></i>

View file

@ -32,7 +32,7 @@
<!-- No Builds --> <!-- No Builds -->
<div class="empty" ng-if="builds && !builds.length"> <div class="empty" ng-if="builds && !builds.length">
<div class="empty-primary-msg">No builds have been run for this repository.</div> <div class="empty-primary-msg">No builds have been run for this repository.</div>
<div class="empty-secondary-msg" ng-if="repository.can_write && !repository.trust_enabled"> <div class="empty-secondary-msg" ng-if="repository.can_write && !repository.tag_operations_disabled">
Click on the <i class="fa fa-tasks" style="margin-left: 6px"></i> Builds tab to start a new build. Click on the <i class="fa fa-tasks" style="margin-left: 6px"></i> Builds tab to start a new build.
</div> </div>
</div> </div>

View file

@ -53,7 +53,7 @@
</li> </li>
<li ng-if="repository.can_write"> <li ng-if="repository.can_write">
<a ng-click="askDeleteMultipleTags(checkedTags.checked)" <a ng-click="askDeleteMultipleTags(checkedTags.checked)"
ng-class="repository.trust_enabled ? 'disabled-option' : ''"> ng-class="repository.tag_operations_disabled ? 'disabled-option' : ''">
<i class="fa fa-times"></i><span class="text">Delete Tags</span> <i class="fa fa-times"></i><span class="text">Delete Tags</span>
</a> </a>
</li> </li>
@ -243,7 +243,7 @@
<span bo-if="repository.can_write"> <span bo-if="repository.can_write">
<span class="cor-options-menu"> <span class="cor-options-menu">
<span class="cor-option" option-click="askAddTag(tag)" <span class="cor-option" option-click="askAddTag(tag)"
ng-class="repository.trust_enabled ? 'disabled-option' : ''"> ng-class="repository.tag_operations_disabled ? 'disabled-option' : ''">
<i class="fa fa-plus"></i> Add New Tag <i class="fa fa-plus"></i> Add New Tag
</span> </span>
<span class="cor-option" option-click="showLabelEditor(tag)" <span class="cor-option" option-click="showLabelEditor(tag)"
@ -251,7 +251,7 @@
<i class="fa fa-tags"></i> Edit Labels <i class="fa fa-tags"></i> Edit Labels
</span> </span>
<span class="cor-option" option-click="askDeleteTag(tag.name)" <span class="cor-option" option-click="askDeleteTag(tag.name)"
ng-class="repository.trust_enabled ? 'disabled-option' : ''"> ng-class="repository.tag_operations_disabled ? 'disabled-option' : ''">
<i class="fa fa-times"></i> Delete Tag <i class="fa fa-times"></i> Delete Tag
</span> </span>
</span> </span>

View file

@ -144,16 +144,17 @@
manifest-digest="restoreTagInfo.manifest_digest"></span>? manifest-digest="restoreTagInfo.manifest_digest"></span>?
</div> </div>
<!-- Trust Enabled Dialog --> <!-- Tag Operations Disabled Dialog -->
<div class="modal fade" id="trustEnabledModal"> <div class="modal fade" id="tagOperationsDisabledModal">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
Cannot execute with trust enabled Tag operations have been disabled.
</div> </div>
<div class="modal-body"> <div class="modal-body">
The selected operation cannot be performed on this repository because Quay Trust is The selected operation cannot be performed on this repository because tag operations have been disabled
enabled, which requires that all operations be signed by a user. by an administrator. <span ng-if="repository.trust_enabled">Trust is enabled for this repo, so any tag changes
should be performed by users with signing keys.</span>
</div> </div>
</div><!-- /.modal-content --> </div><!-- /.modal-content -->
</div><!-- /.modal-dialog --> </div><!-- /.modal-dialog -->

View file

@ -18,7 +18,11 @@
Signing is enabled on this repository and all tag operations must be signed via Docker Content Trust. Signing is enabled on this repository and all tag operations must be signed via Docker Content Trust.
</p> </p>
<p> <p>
Note that due to this feature being enabled, all UI-based tag operations and all build support is <strong>disabled on this repository</strong>. When this feature is enabled, it will be possible to use the UI or client tools to change tag data without
signing.
This can make a signed tag point to a different image than the actual tag, and the underlying data could
be garbage collected. It is important to have a strict separation between tags that are signed and tags
that are not.
</p> </p>
<button class="btn btn-danger" ng-click="$ctrl.askChangeTrust(false)">Disable Trust</button> <button class="btn btn-danger" ng-click="$ctrl.askChangeTrust(false)">Disable Trust</button>
</div> </div>
@ -43,12 +47,14 @@
dialog-title="Enable Trust" dialog-title="Enable Trust"
dialog-action-title="Enable Trust"> dialog-action-title="Enable Trust">
<p>Click "Enable Trust" to enable content trust on this repository.</p> <p>Click "Enable Trust" to enable content trust on this repository.</p>
<p>Please note that at this time, having content trust will <strong>disable</strong> the following <p>Please note that this will not prevent users from overwriting signed tags without updating signatures.
features under the repository: This means that:
<ul> <ul>
<li>Any tag operations in the UI (Add Tag, Delete Tag, Restore Tag) <li>Any tag operations in the UI or client can cause inconsistency
<li>All build triggers and ability to invoke builds <li>Builds should not push to signed tags
</ul> </ul>
We recommend you maintain a strict separation between signed and unsigned tags to avoid any issues with garbage
collection.
</p> </p>
</div> </div>

View file

@ -35,9 +35,9 @@ angular.module('quay').directive('tagOperationsDialog', function () {
}); });
}; };
$scope.alertOnTrust = function() { $scope.alertOnTagOpsDisabled = function() {
if ($scope.repository.trust_enabled) { if ($scope.repository.tag_operations_disabled) {
$('#trustEnabledModal').modal('show'); $('#tagOperationsDisabledModal').modal('show');
return true; return true;
} }
@ -62,7 +62,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
$scope.createOrMoveTag = function(image, tag) { $scope.createOrMoveTag = function(image, tag) {
if (!$scope.repository.can_write) { return; } if (!$scope.repository.can_write) { return; }
if ($scope.alertOnTrust()) { if ($scope.alertOnTagOpsDisabled()) {
return; return;
} }
@ -242,7 +242,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
$scope.actionHandler = { $scope.actionHandler = {
'askDeleteTag': function(tag) { 'askDeleteTag': function(tag) {
if ($scope.alertOnTrust()) { if ($scope.alertOnTagOpsDisabled()) {
return; return;
} }
@ -252,7 +252,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
}, },
'askDeleteMultipleTags': function(tags) { 'askDeleteMultipleTags': function(tags) {
if ($scope.alertOnTrust()) { if ($scope.alertOnTagOpsDisabled()) {
return; return;
} }
@ -262,7 +262,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
}, },
'askAddTag': function(image) { 'askAddTag': function(image) {
if ($scope.alertOnTrust()) { if ($scope.alertOnTagOpsDisabled()) {
return; return;
} }
@ -297,7 +297,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
}, },
'askRestoreTag': function(tag, image_id, opt_manifest_digest) { 'askRestoreTag': function(tag, image_id, opt_manifest_digest) {
if ($scope.alertOnTrust()) { if ($scope.alertOnTagOpsDisabled()) {
return; return;
} }

View file

@ -81,6 +81,7 @@ export type Repository = {
kind?: string; kind?: string;
namespace?: string; namespace?: string;
trust_enabled?: boolean; trust_enabled?: boolean;
tag_operations_disabled?: boolean;
}; };