Add ability for triggers to be disabled

Will be used in the followup commit to automatically disable broken triggers
This commit is contained in:
Joseph Schorr 2017-10-17 17:01:59 -04:00 committed by Joseph Schorr
parent 1e54a4d9e9
commit c35eec0615
18 changed files with 358 additions and 37 deletions

View file

@ -694,6 +694,10 @@ class BuildTriggerService(BaseModel):
name = CharField(index=True, unique=True)
class DisableReason(BaseModel):
name = CharField(index=True, unique=True)
class RepositoryBuildTrigger(BaseModel):
uuid = CharField(default=uuid_generator)
service = ForeignKeyField(BuildTriggerService)
@ -705,6 +709,8 @@ class RepositoryBuildTrigger(BaseModel):
write_token = ForeignKeyField(AccessToken, null=True)
pull_robot = QuayUserField(allows_robots=True, null=True, related_name='triggerpullrobot',
robot_null_delete=True)
enabled = BooleanField(default=True)
disabled_reason = ForeignKeyField(DisableReason, null=True)
class EmailConfirmation(BaseModel):

View file

@ -0,0 +1,56 @@
"""Add ability for build triggers to be disabled
Revision ID: 61cadbacb9fc
Revises: d8989249f8f6
Create Date: 2017-10-18 12:07:26.190901
"""
# revision identifiers, used by Alembic.
revision = '61cadbacb9fc'
down_revision = 'd8989249f8f6'
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
def upgrade(tables):
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('disablereason',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_disablereason'))
)
op.create_index('disablereason_name', 'disablereason', ['name'], unique=True)
op.bulk_insert(
tables.disablereason,
[
{'id': 1, 'name': 'user_toggled'},
],
)
op.bulk_insert(tables.logentrykind, [
{'name': 'toggle_repo_trigger'},
])
op.add_column(u'repositorybuildtrigger', sa.Column('disabled_reason_id', sa.Integer(), nullable=True))
op.add_column(u'repositorybuildtrigger', sa.Column('enabled', sa.Boolean(), nullable=False))
op.create_index('repositorybuildtrigger_disabled_reason_id', 'repositorybuildtrigger', ['disabled_reason_id'], unique=False)
op.create_foreign_key(op.f('fk_repositorybuildtrigger_disabled_reason_id_disablereason'), 'repositorybuildtrigger', 'disablereason', ['disabled_reason_id'], ['id'])
# ### end Alembic commands ###
def downgrade(tables):
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(op.f('fk_repositorybuildtrigger_disabled_reason_id_disablereason'), 'repositorybuildtrigger', type_='foreignkey')
op.drop_index('repositorybuildtrigger_disabled_reason_id', table_name='repositorybuildtrigger')
op.drop_column(u'repositorybuildtrigger', 'enabled')
op.drop_column(u'repositorybuildtrigger', 'disabled_reason_id')
op.drop_table('disablereason')
# ### end Alembic commands ###
op.execute(tables
.logentrykind
.delete()
.where(tables.logentrykind.c.name == op.inline_literal('toggle_repo_trigger')))

View file

@ -10,7 +10,8 @@ Create Date: 2017-03-17 10:00:19.739858
import json
import os
from data.database import RepositoryBuildTrigger
from peewee import *
from data.database import BaseModel
revision = 'a6c463dfb9fe'
down_revision = 'b4df55dea4b3'
@ -18,6 +19,9 @@ down_revision = 'b4df55dea4b3'
from alembic import op
class RepositoryBuildTrigger(BaseModel):
config = TextField(default='{}')
def upgrade(tables):
repostioryBuildTriggers = RepositoryBuildTrigger.select()
for repositoryBuildTrigger in repostioryBuildTriggers:

View file

@ -6,7 +6,8 @@ from peewee import JOIN_LEFT_OUTER
import features
from data.database import (BuildTriggerService, RepositoryBuildTrigger, Repository, Namespace, User,
RepositoryBuild, BUILD_PHASE, db_random_func, UseThenDisconnect)
RepositoryBuild, BUILD_PHASE, db_random_func, UseThenDisconnect,
DisableReason)
from data.model import (InvalidBuildTriggerException, InvalidRepositoryBuildException,
db_transaction, user as user_model, config)
@ -255,3 +256,13 @@ def mark_build_archived(build_uuid):
.where(RepositoryBuild.uuid == build_uuid,
RepositoryBuild.logs_archived == False)
.execute()) > 0
def toggle_build_trigger(trigger, enabled, reason='user_toggled'):
""" Toggles the enabled status of a build trigger. """
trigger.enabled = enabled
if not enabled:
trigger.disabled_reason = DisableReason.get(name=reason)
trigger.save()

View file

@ -22,7 +22,8 @@ from endpoints.api import (RepositoryParamResource, parse_args, query_param, nic
require_repo_read, require_repo_write, validate_json_request,
ApiResource, internal_only, format_date, api, path_param,
require_repo_admin, abort, disallow_for_app_repositories)
from endpoints.building import start_build, PreparedBuild, MaximumBuildsQueuedException
from endpoints.building import (start_build, PreparedBuild, MaximumBuildsQueuedException,
BuildTriggerDisabledException)
from endpoints.exception import Unauthorized, NotFound, InvalidRequest
from util.names import parse_robot_username
@ -69,6 +70,8 @@ def trigger_view(trigger, can_read=False, can_admin=False, for_build=False):
'config': build_trigger.config if can_admin else {},
'can_invoke': can_admin,
'enabled': trigger.enabled,
'disabled_reason': trigger.disabled_reason.name if trigger.disabled_reason else None,
}
if not for_build and can_admin and trigger.pull_robot:
@ -309,6 +312,8 @@ class RepositoryBuildList(RepositoryParamResource):
build_request = start_build(repo, prepared, pull_robot_name=pull_robot_name)
except MaximumBuildsQueuedException:
abort(429, message='Maximum queued build rate exceeded.')
except BuildTriggerDisabledException:
abort(400, message='Build trigger is disabled')
resp = build_status_view(build_request)
repo_string = '%s/%s' % (namespace, repository)

View file

@ -13,7 +13,11 @@ from endpoints.api.signing import RepositorySignatures
from endpoints.api.search import ConductRepositorySearch
from endpoints.api.superuser import SuperUserRepositoryBuildLogs, SuperUserRepositoryBuildResource
from endpoints.api.superuser import SuperUserRepositoryBuildStatus
<<<<<<< HEAD
from endpoints.api.appspecifictokens import AppTokens, AppToken
=======
from endpoints.api.trigger import BuildTrigger
>>>>>>> Add ability for triggers to be disabled
from endpoints.test.shared import client_with_identity, toggle_feature
from test.fixtures import *
@ -24,6 +28,7 @@ REPO_PARAMS = {'repository': 'devtable/someapp'}
SEARCH_PARAMS = {'query': ''}
NOTIFICATION_PARAMS = {'namespace': 'devtable', 'repository': 'devtable/simple', 'uuid': 'some uuid'}
TOKEN_PARAMS = {'token_uuid': 'someuuid'}
TRIGGER_PARAMS = {'repository': 'devtable/simple', 'trigger_uuid': 'someuuid'}
@pytest.mark.parametrize('resource,method,params,body,identity,expected', [
(AppTokens, 'GET', {}, {}, None, 401),
@ -89,7 +94,22 @@ TOKEN_PARAMS = {'token_uuid': 'someuuid'}
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, 'freshuser', 403),
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, 'reader', 403),
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, 'devtable', 404),
(BuildTrigger, 'GET', TRIGGER_PARAMS, {}, None, 401),
(BuildTrigger, 'GET', TRIGGER_PARAMS, {}, 'freshuser', 403),
(BuildTrigger, 'GET', TRIGGER_PARAMS, {}, 'reader', 403),
(BuildTrigger, 'GET', TRIGGER_PARAMS, {}, 'devtable', 404),
(BuildTrigger, 'DELETE', TRIGGER_PARAMS, {}, None, 403),
(BuildTrigger, 'DELETE', TRIGGER_PARAMS, {}, 'freshuser', 403),
(BuildTrigger, 'DELETE', TRIGGER_PARAMS, {}, 'reader', 403),
(BuildTrigger, 'DELETE', TRIGGER_PARAMS, {}, 'devtable', 404),
(BuildTrigger, 'PUT', TRIGGER_PARAMS, {}, None, 403),
(BuildTrigger, 'PUT', TRIGGER_PARAMS, {}, 'freshuser', 403),
(BuildTrigger, 'PUT', TRIGGER_PARAMS, {}, 'reader', 403),
(BuildTrigger, 'PUT', TRIGGER_PARAMS, {}, 'devtable', 400),
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'public/publicrepo'}, None, None, 401),
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'public/publicrepo'}, None, 'freshuser', 403),
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'public/publicrepo'}, None, 'reader', 403),

View file

@ -1,6 +1,12 @@
import pytest
import json
from data import model
from endpoints.api.trigger_analyzer import is_parent
from endpoints.api.trigger import BuildTrigger
from endpoints.api.test.shared import conduct_api_call
from endpoints.test.shared import client_with_identity
from test.fixtures import *
@pytest.mark.parametrize('context,dockerfile_path,expected', [
@ -20,3 +26,30 @@ from endpoints.api.trigger_analyzer import is_parent
])
def test_super_user_build_endpoints(context, dockerfile_path, expected):
assert is_parent(context, dockerfile_path) == expected
def test_enabled_disabled_trigger(app, client):
trigger = model.build.list_build_triggers('devtable', 'building')[0]
trigger.config = json.dumps({'hook_id': 'someid'})
trigger.save()
params = {
'repository': 'devtable/building',
'trigger_uuid': trigger.uuid,
}
body = {
'enabled': False,
}
with client_with_identity('devtable', client) as cl:
result = conduct_api_call(cl, BuildTrigger, 'PUT', params, body, 200).json
assert not result['enabled']
body = {
'enabled': True,
}
with client_with_identity('devtable', client) as cl:
result = conduct_api_call(cl, BuildTrigger, 'PUT', params, body, 200).json
assert result['enabled']

View file

@ -22,7 +22,8 @@ from endpoints.api import (RepositoryParamResource, nickname, resource, require_
disallow_for_app_repositories)
from endpoints.api.build import build_status_view, trigger_view, RepositoryBuildStatus
from endpoints.api.trigger_analyzer import TriggerAnalyzer
from endpoints.building import start_build, MaximumBuildsQueuedException
from endpoints.building import (start_build, MaximumBuildsQueuedException,
BuildTriggerDisabledException)
from endpoints.exception import NotFound, Unauthorized, InvalidRequest
from util.names import parse_robot_username
@ -62,6 +63,21 @@ class BuildTriggerList(RepositoryParamResource):
@path_param('trigger_uuid', 'The UUID of the build trigger')
class BuildTrigger(RepositoryParamResource):
""" Resource for managing specific build triggers. """
schemas = {
'UpdateTrigger': {
'type': 'object',
'description': 'Options for updating a build trigger',
'required': [
'enabled',
],
'properties': {
'enabled': {
'type': 'boolean',
'description': 'Whether the build trigger is enabled',
},
}
},
}
@require_repo_admin
@disallow_for_app_repositories
@ -70,6 +86,27 @@ class BuildTrigger(RepositoryParamResource):
""" Get information for the specified build trigger. """
return trigger_view(get_trigger(trigger_uuid), can_admin=True)
@require_repo_admin
@disallow_for_app_repositories
@nickname('updateBuildTrigger')
@validate_json_request('UpdateTrigger')
def put(self, namespace_name, repo_name, trigger_uuid):
""" Updates the specified build trigger. """
trigger = get_trigger(trigger_uuid)
handler = BuildTriggerHandler.get_handler(trigger)
if not handler.is_active():
raise InvalidRequest('Cannot update an unactivated trigger')
enable = request.get_json()['enabled']
model.build.toggle_build_trigger(trigger, enable)
log_action('toggle_repo_trigger', namespace_name,
{'repo': repo_name, 'trigger_id': trigger_uuid,
'service': trigger.service.name, 'enabled': enable},
repo=model.repository.get_repository(namespace_name, repo_name))
return trigger_view(trigger)
@require_repo_admin
@disallow_for_app_repositories
@nickname('deleteBuildTrigger')
@ -340,6 +377,8 @@ class ActivateBuildTrigger(RepositoryParamResource):
def post(self, namespace_name, repo_name, trigger_uuid):
""" Manually start a build from the specified trigger. """
trigger = get_trigger(trigger_uuid)
if not trigger.enabled:
raise InvalidRequest('Trigger is not enabled.')
handler = BuildTriggerHandler.get_handler(trigger)
if not handler.is_active():
@ -356,6 +395,8 @@ class ActivateBuildTrigger(RepositoryParamResource):
raise InvalidRequest(tse.message)
except MaximumBuildsQueuedException:
abort(429, message='Maximum queued build rate exceeded.')
except BuildTriggerDisabledException:
abort(400, message='Build trigger is disabled')
resp = build_status_view(build_request)
repo_string = '%s/%s' % (namespace_name, repo_name)
@ -485,3 +526,4 @@ class BuildTriggerSourceNamespaces(RepositoryParamResource):
raise InvalidRequest(rre.message)
else:
raise Unauthorized()

View file

@ -25,10 +25,22 @@ class MaximumBuildsQueuedException(Exception):
pass
class BuildTriggerDisabledException(Exception):
"""
This exception is raised when a build is required, but the build trigger has been disabled.
"""
pass
def start_build(repository, prepared_build, pull_robot_name=None):
# Ensure that builds are only run in image repositories.
if repository.kind.name != 'image':
raise Exception('Attempt to start a build for application repository %s' % repository.id)
# Ensure that disabled triggers are not run.
if prepared_build.trigger is not None and not prepared_build.trigger.enabled:
raise BuildTriggerDisabledException
if repository.namespace_user.maximum_queued_builds_count is not None:
queue_item_canonical_name = [repository.namespace_user.username]
alive_builds = dockerfile_build_queue.num_alive_jobs(queue_item_canonical_name)

View file

@ -1,7 +1,8 @@
import pytest
from data import model
from endpoints.building import start_build, PreparedBuild, MaximumBuildsQueuedException
from endpoints.building import (start_build, PreparedBuild, MaximumBuildsQueuedException,
BuildTriggerDisabledException)
from test.fixtures import *
@ -29,3 +30,14 @@ def test_maximum_builds(app):
# Try to queue a second build; should fail.
with pytest.raises(MaximumBuildsQueuedException):
start_build(repo, prepared_build)
def test_start_build_disabled_trigger(app):
trigger = model.build.list_build_triggers('devtable', 'building')[0]
trigger.enabled = False
trigger.save()
build = PreparedBuild(trigger=trigger)
with pytest.raises(BuildTriggerDisabledException):
start_build(trigger.repository, build)

View file

@ -0,0 +1,24 @@
import base64
import pytest
from flask import url_for
from data import model
from endpoints.test.shared import conduct_call
from test.fixtures import *
def test_start_build_disabled_trigger(app, client):
trigger = model.build.list_build_triggers('devtable', 'building')[0]
trigger.enabled = False
trigger.save()
params = {
'trigger_uuid': trigger.uuid,
}
headers = {
'Authorization': 'Basic ' + base64.b64encode('devtable:password'),
}
conduct_call(client, 'webhooks.build_trigger_webhook', url_for, 'POST', params, None, 400,
headers=headers)

View file

@ -12,7 +12,8 @@ from util.http import abort
from buildtrigger.basehandler import BuildTriggerHandler
from buildtrigger.triggerutil import (ValidationRequestException, SkipRequestException,
InvalidPayloadException)
from endpoints.building import start_build, MaximumBuildsQueuedException
from endpoints.building import (start_build, MaximumBuildsQueuedException,
BuildTriggerDisabledException)
logger = logging.getLogger(__name__)
@ -91,9 +92,7 @@ def build_trigger_webhook(trigger_uuid, **kwargs):
namespace = trigger.repository.namespace_user.username
repository = trigger.repository.name
permission = ModifyRepositoryPermission(namespace, repository)
if permission.can():
if ModifyRepositoryPermission(namespace, repository).can():
handler = BuildTriggerHandler.get_handler(trigger)
if trigger.repository.kind.name != 'image':
@ -121,6 +120,9 @@ def build_trigger_webhook(trigger_uuid, **kwargs):
start_build(repo, prepared, pull_robot_name=pull_robot_name)
except MaximumBuildsQueuedException:
abort(429, message='Maximum queued build rate exceeded.')
except BuildTriggerDisabledException:
logger.debug('Build trigger %s is disabled', trigger_uuid)
abort(400, message='This build trigger is currently disabled. Please re-enable to continue.')
return make_response('Okay')

View file

@ -20,7 +20,7 @@ from data.database import (db, all_models, beta_classes, Role, TeamRole, Visibil
ExternalNotificationEvent, ExternalNotificationMethod, NotificationKind,
QuayRegion, QuayService, UserRegion, OAuthAuthorizationCode,
ServiceKeyApprovalType, MediaType, LabelSourceType, UserPromptKind,
RepositoryKind, TagKind, BlobPlacementLocation, User,
RepositoryKind, TagKind, BlobPlacementLocation, User, DisableReason,
DeletedNamespace)
from data import model
from data.queue import WorkQueue
@ -353,6 +353,7 @@ def initialize_database():
LogEntryKind.create(name='manifest_label_delete')
LogEntryKind.create(name='change_tag_expiration')
LogEntryKind.create(name='toggle_repo_trigger')
LogEntryKind.create(name='create_app_specific_token')
LogEntryKind.create(name='revoke_app_specific_token')
@ -434,6 +435,8 @@ def initialize_database():
TagKind.create(name='release')
TagKind.create(name='channel')
DisableReason.create(name='user_toggled')
def wipe_database():
logger.debug('Wiping all data from the DB.')

View file

@ -13,4 +13,22 @@
.repo-panel-builds .heading-controls {
white-space: nowrap;
}
}
.repo-panel-builds .trigger-disabled {
background-color: #fcfcfc;
}
.repo-panel-builds .trigger-disabled td {
border-bottom: 0px;
color: #ccc;
}
.repo-panel-builds .trigger-disabled-message {
font-size: 13px;
}
.repo-panel-builds i.fa-exclamation-triangle {
color: #f5c77d;
margin-right: 4px;
}

View file

@ -137,31 +137,45 @@
<a ng-click="deleteTrigger(trigger)">Delete Trigger</a>
</td>
</tr>
<tr ng-repeat="trigger in triggers | filter:{'is_active':true}">
<td><trigger-description trigger="trigger"></trigger-description></td>
<td>{{ trigger.config.dockerfile_path || '/Dockerfile' }}</td>
<td>{{ trigger.config.context || '/' }}</td>
<td>{{ trigger.config.branchtag_regex || 'All' }}</td>
<td>
<span class="entity-reference" entity="trigger.pull_robot" ng-if="trigger.pull_robot"></span>
<span class="empty" ng-if="!trigger.pull_robot">(None)</span>
</td>
<td>
<span class="cor-options-menu">
<span ng-if="trigger.config.credentials" class="cor-option" option-click="showTriggerCredentialsModal(trigger)">
<i class="fa fa-unlock-alt"></i> View Credentials
<tbody ng-repeat="trigger in triggers | filter:{'is_active':true}">
<tr ng-class="{'trigger-disabled': !trigger.enabled}">
<td><trigger-description trigger="trigger"></trigger-description></td>
<td>{{ trigger.config.dockerfile_path || '/Dockerfile' }}</td>
<td>{{ trigger.config.context || '/' }}</td>
<td>{{ trigger.config.branchtag_regex || 'All' }}</td>
<td>
<span class="entity-reference" entity="trigger.pull_robot" ng-if="trigger.pull_robot"></span>
<span class="empty" ng-if="!trigger.pull_robot">(None)</span>
</td>
<td>
<span class="cor-options-menu">
<span ng-if="trigger.config.credentials" class="cor-option" option-click="showTriggerCredentialsModal(trigger)">
<i class="fa fa-unlock-alt"></i> View Credentials
</span>
<span class="cor-option" option-click="askRunTrigger(trigger)"
ng-class="trigger.can_invoke && trigger.enabled ? '' : 'disabled'">
<i class="fa fa-chevron-right"></i> Run Trigger Now
</span>
<span class="cor-option" option-click="askToggleTrigger(trigger)">
<i class="fa fa-adjust"></i>
<span ng-if="trigger.enabled">Disable Trigger</span>
<span ng-if="!trigger.enabled">Enable Trigger</span>
</span>
<span class="cor-option" option-click="askDeleteTrigger(trigger)">
<i class="fa fa-times"></i> Delete Trigger
</span>
</span>
<span class="cor-option" option-click="askRunTrigger(trigger)"
ng-class="trigger.can_invoke ? '' : 'disabled'">
<i class="fa fa-chevron-right"></i> Run Trigger Now
</span>
<span class="cor-option" option-click="askDeleteTrigger(trigger)">
<i class="fa fa-times"></i> Delete Trigger
</span>
</span>
</td>
</tr>
</td>
</tr>
<tr class="trigger-disabled-message" ng-if="!trigger.enabled">
<td colspan="5" style="text-align: center">
<i class="fa fa-exclamation-triangle"></i>
This build trigger is currently disabled and will not build:
<a ng-click="askToggleTrigger(trigger)">Re-enable this trigger</a>
</td>
</tr>
</tbody>
</table>
@ -174,7 +188,16 @@
<!-- Trigger Credentials dialog -->
<div class="trigger-credentials-dialog" trigger="triggerCredentialsModalTrigger" counter="triggerCredentialsModalCounter"></div>
<!-- Delete Tag Confirm -->
<!-- Toggle Trigger Confirm -->
<div class="cor-confirm-dialog"
dialog-context="toggleTriggerInfo"
dialog-action="toggleTrigger(info.trigger, callback)"
dialog-title="Toggle Trigger"
dialog-action-title="Toggle Trigger">
Are you sure you want to <span ng-if="toggleTriggerInfo.trigger.enabled">disable</span><span ng-if="!toggleTriggerInfo.trigger.enabled">enable</span> this trigger?
</div>
<!-- Delete Trigger Confirm -->
<div class="cor-confirm-dialog"
dialog-context="deleteTriggerInfo"
dialog-action="deleteTrigger(info.trigger, callback)"

View file

@ -187,6 +187,10 @@ angular.module('quay').directive('repoPanelBuilds', function () {
};
$scope.askRunTrigger = function(trigger) {
if (!trigger.enabled) {
return;
}
if (!trigger.can_invoke) {
bootbox.alert('You do not have permission to manually invoke this trigger');
return;
@ -196,6 +200,40 @@ angular.module('quay').directive('repoPanelBuilds', function () {
$scope.showTriggerStartDialogCounter++;
};
$scope.askToggleTrigger = function(trigger) {
if (!trigger.can_invoke) {
bootbox.alert('You do not have permission to edit this trigger');
return;
}
$scope.toggleTriggerInfo = {
'trigger': trigger
};
};
$scope.toggleTrigger = function(trigger, opt_callback) {
if (!trigger) { return; }
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'trigger_uuid': trigger.id
};
var data = {
'enabled': !trigger.enabled
};
var errorHandler = ApiService.errorDisplay('Could not toggle build trigger', function() {
opt_callback && opt_callback(false);
});
ApiService.updateBuildTrigger(data, params).then(function(resp) {
trigger.enabled = !trigger.enabled;
trigger.disabled_reason = 'user_toggled';
opt_callback && opt_callback(true);
}, errorHandler);
};
$scope.deleteTrigger = function(trigger, opt_callback) {
if (!trigger) { return; }

View file

@ -223,6 +223,15 @@ angular.module('quay').directive('logsView', function () {
metadata['service'], metadata['config']);
return 'Delete build trigger - ' + triggerDescription;
},
'toggle_repo_trigger': function(metadata) {
var triggerDescription = TriggerService.getDescription(
metadata['service'], metadata['config']);
if (metadata.enabled) {
return 'Build trigger enabled - ' + triggerDescription;
} else {
return 'Build trigger disabled - ' + triggerDescription;
}
},
'create_application': 'Create application {application_name} with client ID {client_id}',
'update_application': 'Update application to {application_name} for client ID {client_id}',
'delete_application': 'Delete application {application_name} with client ID {client_id}',
@ -330,6 +339,7 @@ angular.module('quay').directive('logsView', function () {
'delete_prototype_permission': 'Delete default permission',
'setup_repo_trigger': 'Setup build trigger',
'delete_repo_trigger': 'Delete build trigger',
'toggle_repo_trigger': 'Enable/disable build trigger',
'create_application': 'Create Application',
'update_application': 'Update Application',
'delete_application': 'Delete Application',

View file

@ -17,6 +17,7 @@ from endpoints.appr import appr_bp
from endpoints.web import web
from endpoints.v2 import v2_bp
from endpoints.verbs import verbs as verbs_bp
from endpoints.webhooks import webhooks
from initdb import initialize_database, populate_database
@ -175,6 +176,7 @@ def app(appconfig, initialized_db):
app.register_blueprint(web, url_prefix='/')
app.register_blueprint(verbs_bp, url_prefix='/c1')
app.register_blueprint(v2_bp, url_prefix='/v2')
app.register_blueprint(webhooks, url_prefix='/webhooks')
app.config.update(appconfig)
return app