launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #27000
[Merge] ~twom/launchpad:oci-policy-enforce-branch-format-on-api into launchpad:master
Tom Wardill has proposed merging ~twom/launchpad:oci-policy-enforce-branch-format-on-api into launchpad:master.
Commit message:
Enforce the ROCKs branch format at the API level
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~twom/launchpad/+git/launchpad/+merge/402208
Prevent creating a new OCIRecipe with an invalid branch format name at all, rather than just via the UI.
* Make sure we get a nice HTTP error on the API, the UI has it's own error handling for that case.
* Reuse the existing validator so it's centralised.
* Update the tests to use the new format
* Allow existing recipes with the old format to still function
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~twom/launchpad:oci-policy-enforce-branch-format-on-api into launchpad:master.
diff --git a/lib/lp/oci/browser/tests/test_ocirecipe.py b/lib/lp/oci/browser/tests/test_ocirecipe.py
index 2f46c22..9dc3fc3 100644
--- a/lib/lp/oci/browser/tests/test_ocirecipe.py
+++ b/lib/lp/oci/browser/tests/test_ocirecipe.py
@@ -13,7 +13,6 @@ from datetime import (
timedelta,
)
from operator import attrgetter
-import re
from fixtures import FakeLogger
import pytz
@@ -651,7 +650,8 @@ class TestOCIRecipeEditView(OCIConfigHelperMixin, BaseTestOCIRecipeView):
def test_edit_recipe(self):
oci_project = self.factory.makeOCIProject()
oci_project_display = oci_project.display_name
- [old_git_ref] = self.factory.makeGitRefs()
+ [old_git_ref] = self.factory.makeGitRefs(
+ paths=['refs/heads/v1.0-20.04'])
recipe = self.factory.makeOCIRecipe(
registrant=self.person, owner=self.person,
oci_project=oci_project, git_ref=old_git_ref)
@@ -696,7 +696,8 @@ class TestOCIRecipeEditView(OCIConfigHelperMixin, BaseTestOCIRecipeView):
def test_edit_recipe_invalid_branch(self):
oci_project = self.factory.makeOCIProject()
repository = self.factory.makeGitRepository()
- [old_git_ref] = self.factory.makeGitRefs(repository=repository)
+ [old_git_ref] = self.factory.makeGitRefs(
+ paths=['refs/heads/v1.0-20.04'], repository=repository)
recipe = self.factory.makeOCIRecipe(
registrant=self.person, owner=self.person,
oci_project=oci_project, git_ref=old_git_ref)
@@ -966,7 +967,8 @@ class TestOCIRecipeEditView(OCIConfigHelperMixin, BaseTestOCIRecipeView):
self.setUpDistroSeries()
repo = self.factory.makeGitRepository(
owner=self.person, registrant=self.person)
- [git_ref] = self.factory.makeGitRefs(repository=repo)
+ [git_ref] = self.factory.makeGitRefs(
+ repository=repo, paths=['refs/heads/v1.0-20.04'])
oci_project = self.factory.makeOCIProject(
registrant=self.person, pillar=self.distribution)
recipe = self.factory.makeOCIRecipe(
@@ -986,7 +988,8 @@ class TestOCIRecipeEditView(OCIConfigHelperMixin, BaseTestOCIRecipeView):
self.setUpDistroSeries()
oci_project = self.factory.makeOCIProject(
registrant=self.person, pillar=self.distribution)
- [random_git_ref] = self.factory.makeGitRefs()
+ [random_git_ref] = self.factory.makeGitRefs(
+ paths=['refs/heads/v1.0-20.04'])
recipe = self.factory.makeOCIRecipe(
registrant=self.person, owner=self.person, oci_project=oci_project,
git_ref=random_git_ref)
@@ -1018,7 +1021,8 @@ class TestOCIRecipeEditView(OCIConfigHelperMixin, BaseTestOCIRecipeView):
name=oci_project.name,
target=oci_project, owner=self.person, registrant=self.person)
- [git_ref] = self.factory.makeGitRefs(repository=default_repo)
+ [git_ref] = self.factory.makeGitRefs(
+ repository=default_repo, paths=['refs/heads/v1.0-20.04'])
recipe = self.factory.makeOCIRecipe(
registrant=self.person, owner=self.person, oci_project=oci_project,
git_ref=git_ref)
@@ -1035,7 +1039,7 @@ class TestOCIRecipeEditView(OCIConfigHelperMixin, BaseTestOCIRecipeView):
oci_project = self.factory.makeOCIProject(
registrant=self.person, pillar=self.distribution)
- [git_ref] = self.factory.makeGitRefs()
+ [git_ref] = self.factory.makeGitRefs(paths=['refs/heads/v1.0-20.04'])
recipe = self.factory.makeOCIRecipe(
registrant=self.person, owner=self.person, oci_project=oci_project,
git_ref=git_ref)
@@ -1290,7 +1294,7 @@ class TestOCIRecipeView(BaseTestOCIRecipeView):
oci_project_display = oci_project.display_name
[ref] = self.factory.makeGitRefs(
owner=self.person, target=self.person, name="recipe-repository",
- paths=["refs/heads/master"])
+ paths=["refs/heads/v1.0-20.04"])
recipe = self.makeRecipe(
processor_names=["amd64", "386"],
build_file="Dockerfile", git_ref=ref,
@@ -1320,7 +1324,7 @@ class TestOCIRecipeView(BaseTestOCIRecipeView):
OCI recipe information
Owner: Test Person
OCI project: %s
- Source: ~test-person/\\+git/recipe-repository:master
+ Source: ~test-person/\\+git/recipe-repository:v1.0-20.04
Build file path: Dockerfile
Build context directory: %s
Build schedule: Built on request
@@ -1385,11 +1389,10 @@ class TestOCIRecipeView(BaseTestOCIRecipeView):
def test_index_with_build_args(self):
oci_project = self.factory.makeOCIProject(
pillar=self.distroseries.distribution)
- oci_project_name = oci_project.name
oci_project_display = oci_project.display_name
[ref] = self.factory.makeGitRefs(
owner=self.person, target=self.person, name="recipe-repository",
- paths=["refs/heads/master"])
+ paths=["refs/heads/v1.0-20.04"])
recipe = self.makeOCIRecipe(
oci_project=oci_project, git_ref=ref, build_file="Dockerfile",
build_args={"VAR1": "123", "VAR2": "XXX"})
@@ -1403,7 +1406,7 @@ class TestOCIRecipeView(BaseTestOCIRecipeView):
OCI recipe information
Owner: Test Person
OCI project: %s
- Source: ~test-person/\\+git/recipe-repository:master
+ Source: ~test-person/\\+git/recipe-repository:v1.0-20.04
Build file path: Dockerfile
Build context directory: %s
Build schedule: Built on request
@@ -1429,11 +1432,10 @@ class TestOCIRecipeView(BaseTestOCIRecipeView):
def test_index_for_subscriber_without_git_repo_access(self):
oci_project = self.factory.makeOCIProject(
pillar=self.distroseries.distribution)
- oci_project_name = oci_project.name
oci_project_display = oci_project.display_name
[ref] = self.factory.makeGitRefs(
owner=self.person, target=self.person, name="recipe-repository",
- paths=["refs/heads/master"],
+ paths=["refs/heads/v1.0-20.04"],
information_type=InformationType.PRIVATESECURITY)
recipe = self.makeOCIRecipe(
oci_project=oci_project, git_ref=ref, build_file="Dockerfile",
@@ -1514,7 +1516,7 @@ class TestOCIRecipeView(BaseTestOCIRecipeView):
pillar=self.distroseries.distribution)
[ref] = self.factory.makeGitRefs(
owner=self.person, target=self.person, name="recipe-repository",
- paths=["refs/heads/master"])
+ paths=["refs/heads/v1.0-20.04"])
recipe = self.makeRecipe(
processor_names=["amd64", "386"],
build_file="Dockerfile", git_ref=ref,
diff --git a/lib/lp/oci/browser/tests/test_ocirecipesubscription.py b/lib/lp/oci/browser/tests/test_ocirecipesubscription.py
index 7f23b06..1f60710 100644
--- a/lib/lp/oci/browser/tests/test_ocirecipesubscription.py
+++ b/lib/lp/oci/browser/tests/test_ocirecipesubscription.py
@@ -42,7 +42,7 @@ class BaseTestOCIRecipeView(OCIConfigHelperMixin, BrowserTestCase):
def makeOCIRecipe(self, oci_project=None, **kwargs):
[ref] = self.factory.makeGitRefs(
owner=self.person, target=self.person, name="recipe-repository",
- paths=["refs/heads/master"])
+ paths=["refs/heads/v1.0-20.04"])
if oci_project is None:
project = self.factory.makeProduct(
owner=self.person, registrant=self.person)
diff --git a/lib/lp/oci/interfaces/ocirecipe.py b/lib/lp/oci/interfaces/ocirecipe.py
index bf83392..2c1179d 100644
--- a/lib/lp/oci/interfaces/ocirecipe.py
+++ b/lib/lp/oci/interfaces/ocirecipe.py
@@ -19,6 +19,7 @@ __all__ = [
'NoSuchOCIRecipe',
'OCIRecipeBuildAlreadyPending',
'OCIRecipeFeatureDisabled',
+ 'OCIRecipeBranchHasInvalidFormat',
'OCIRecipeNotOwner',
'OCIRecipePrivacyMismatch',
'OCI_RECIPE_ALLOW_CREATE',
@@ -164,6 +165,15 @@ class OCIRecipePrivacyMismatch(Exception):
"OCI recipe contains private information and cannot be public.")
+@error_status(http_client.BAD_REQUEST)
+class OCIRecipeBranchHasInvalidFormat(Exception):
+ """The branch name for the OCI recipe does not match the correct format."""
+
+ def __init__(self):
+ super(OCIRecipeBranchHasInvalidFormat, self).__init__(
+ "The branch name for the OCI recipe does not "
+ "match the correct format.")
+
@exported_as_webservice_entry(
publish_web_link=True, as_of="devel",
singular_name="oci_recipe_build_request")
diff --git a/lib/lp/oci/model/ocirecipe.py b/lib/lp/oci/model/ocirecipe.py
index adcbd6c..c16be3e 100644
--- a/lib/lp/oci/model/ocirecipe.py
+++ b/lib/lp/oci/model/ocirecipe.py
@@ -87,6 +87,7 @@ from lp.oci.interfaces.ocirecipe import (
OCI_RECIPE_BUILD_DISTRIBUTION,
OCIRecipeBuildAlreadyPending,
OCIRecipeFeatureDisabled,
+ OCIRecipeBranchHasInvalidFormat,
OCIRecipeNotOwner,
OCIRecipePrivacyMismatch,
UsingDistributionCredentials,
@@ -841,6 +842,9 @@ class OCIRecipeSet:
if self.exists(owner, oci_project, name):
raise DuplicateOCIRecipeName
+ if not validate_oci_branch_name(git_ref.name):
+ raise OCIRecipeBranchHasInvalidFormat
+
if build_path is None:
build_path = "."
diff --git a/lib/lp/oci/tests/test_ocirecipe.py b/lib/lp/oci/tests/test_ocirecipe.py
index 8cd3705..95e30a8 100644
--- a/lib/lp/oci/tests/test_ocirecipe.py
+++ b/lib/lp/oci/tests/test_ocirecipe.py
@@ -848,7 +848,8 @@ class TestOCIRecipe(OCIConfigHelperMixin, TestCaseWithFactory):
[private_git_ref] = self.factory.makeGitRefs(
target=pillar, owner=owner,
- information_type=InformationType.PROPRIETARY)
+ information_type=InformationType.PROPRIETARY,
+ paths=['refs/heads/v1.0-20.04'])
private_recipe = self.factory.makeOCIRecipe(
owner=private_team, registrant=owner,
@@ -1092,7 +1093,8 @@ class TestOCIRecipeProcessors(TestCaseWithFactory):
recipe = getUtility(IOCIRecipeSet).new(
name=self.factory.getUniqueUnicode(), registrant=owner,
owner=owner, oci_project=oci_project,
- git_ref=self.factory.makeGitRefs()[0],
+ git_ref=self.factory.makeGitRefs(
+ paths=['refs/heads/v1.0-20.04'])[0],
build_file=self.factory.getUniqueUnicode())
self.assertContentEqual(
["386", "amd64", "hppa", "default"],
@@ -1106,7 +1108,8 @@ class TestOCIRecipeProcessors(TestCaseWithFactory):
recipe = getUtility(IOCIRecipeSet).new(
name=self.factory.getUniqueUnicode(), registrant=owner,
owner=owner, oci_project=oci_project,
- git_ref=self.factory.makeGitRefs()[0],
+ git_ref=self.factory.makeGitRefs(
+ paths=['refs/heads/v1.0-20.04'])[0],
build_file=self.factory.getUniqueUnicode(), processors=[self.arm])
self.assertContentEqual(
["arm"], [processor.name for processor in recipe.processors])
@@ -1164,20 +1167,25 @@ class TestOCIRecipeProcessors(TestCaseWithFactory):
self.assertTrue(recipe.is_valid_branch_format)
def test_valid_branch_format_invalid(self):
+ # We can't use OCIRecipeSet.new with an invalid path
+ # so create a valid one, then change it after
+ recipe = self.factory.makeOCIRecipe()
[git_ref] = self.factory.makeGitRefs(paths=["refs/heads/v1.0-foo"])
- recipe = self.factory.makeOCIRecipe(git_ref=git_ref)
+ recipe.git_ref = git_ref
self.assertFalse(recipe.is_valid_branch_format)
def test_valid_branch_format_invalid_uses_risk(self):
for risk in ["stable", "candidate", "beta", "edge"]:
+ recipe = self.factory.makeOCIRecipe()
path = "refs/heads/{}-20.04".format(risk)
[git_ref] = self.factory.makeGitRefs(paths=[path])
- recipe = self.factory.makeOCIRecipe(git_ref=git_ref)
+ recipe.git_ref = git_ref
self.assertFalse(recipe.is_valid_branch_format)
def test_valid_branch_format_invalid_with_slash(self):
+ recipe = self.factory.makeOCIRecipe()
[git_ref] = self.factory.makeGitRefs(paths=["refs/heads/v1.0/bar-foo"])
- recipe = self.factory.makeOCIRecipe(git_ref=git_ref)
+ recipe.git_ref = git_ref
self.assertFalse(recipe.is_valid_branch_format)
@@ -1198,7 +1206,8 @@ class TestOCIRecipeSet(TestCaseWithFactory):
registrant = self.factory.makePerson()
owner = self.factory.makeTeam(members=[registrant])
oci_project = self.factory.makeOCIProject()
- [git_ref] = self.factory.makeGitRefs()
+ [git_ref] = self.factory.makeGitRefs(
+ paths=['refs/heads/v1.0-20.04'])
target = getUtility(IOCIRecipeSet).new(
name='a name',
registrant=registrant,
@@ -1251,7 +1260,8 @@ class TestOCIRecipeSet(TestCaseWithFactory):
owner = self.factory.makePerson()
oci_project = self.factory.makeOCIProject()
recipe_set = getUtility(IOCIRecipeSet)
- [git_ref] = self.factory.makeGitRefs()
+ [git_ref]=self.factory.makeGitRefs(
+ paths=['refs/heads/v1.0-20.04']),
self.assertRaises(
NoSourceForOCIRecipe,
recipe_set.new,
@@ -1295,7 +1305,9 @@ class TestOCIRecipeSet(TestCaseWithFactory):
oci_recipes = []
for repository in repositories:
for i in range(2):
- [ref] = self.factory.makeGitRefs(repository=repository)
+ [ref] = self.factory.makeGitRefs(
+ repository=repository,
+ paths=['refs/heads/v1.0-20.04'])
oci_recipes.append(self.factory.makeOCIRecipe(git_ref=ref))
oci_recipe_set = getUtility(IOCIRecipeSet)
self.assertContentEqual(
@@ -1311,7 +1323,10 @@ class TestOCIRecipeSet(TestCaseWithFactory):
oci_recipes = []
for repository in repositories:
for i in range(3):
- [ref] = self.factory.makeGitRefs(repository=repository)
+ [ref] = self.factory.makeGitRefs(
+ repository=repository,
+ # Needs a unique path, otherwise we can't search for it.
+ paths=['refs/heads/v1.{}-20.04'.format(str(i))])
oci_recipes.append(self.factory.makeOCIRecipe(git_ref=ref))
oci_recipe_set = getUtility(IOCIRecipeSet)
self.assertContentEqual(
@@ -1334,7 +1349,9 @@ class TestOCIRecipeSet(TestCaseWithFactory):
refs = []
for repository in repositories:
for i in range(2):
- [ref] = self.factory.makeGitRefs(repository=repository)
+ [ref] = self.factory.makeGitRefs(
+ repository=repository,
+ paths=['refs/heads/v1.0-20.04'])
paths.append(ref.path)
refs.append(ref)
oci_recipes.append(self.factory.makeOCIRecipe(
@@ -1458,7 +1475,8 @@ class TestOCIRecipeWebservice(OCIConfigHelperMixin, TestCaseWithFactory):
owner=self.person)
oci_project = self.factory.makeOCIProject(
pillar=distro, registrant=self.person)
- git_ref = self.factory.makeGitRefs()[0]
+ [git_ref] = self.factory.makeGitRefs(
+ paths=['refs/heads/v1.0-20.04'])
oci_project_url = api_url(oci_project)
git_ref_url = api_url(git_ref)
@@ -1490,6 +1508,30 @@ class TestOCIRecipeWebservice(OCIConfigHelperMixin, TestCaseWithFactory):
build_args=Equals({"VAR": "VAR VALUE"})
)))
+ def test_api_create_oci_recipe_invalid_branch_format(self):
+ with person_logged_in(self.person):
+ distro = self.factory.makeDistribution(
+ owner=self.person)
+ oci_project = self.factory.makeOCIProject(
+ pillar=distro, registrant=self.person)
+ [git_ref] = self.factory.makeGitRefs(
+ paths=['refs/heads/invalid-branch'])
+
+ oci_project_url = api_url(oci_project)
+ git_ref_url = api_url(git_ref)
+ person_url = api_url(self.person)
+
+ obj = {
+ "name": "my-recipe",
+ "owner": person_url,
+ "git_ref": git_ref_url,
+ "build_file": "./Dockerfile",
+ "build_args": {"VAR": "VAR VALUE"},
+ "description": "My recipe"}
+
+ resp = self.webservice.named_post(oci_project_url, "newRecipe", **obj)
+ self.assertEqual(400, resp.status, resp.body)
+
def test_api_create_oci_recipe_non_legitimate_user(self):
"""Ensure that a non-legitimate user cannot create recipe using API"""
self.pushConfig(
@@ -1527,7 +1569,8 @@ class TestOCIRecipeWebservice(OCIConfigHelperMixin, TestCaseWithFactory):
owner=self.person)
oci_project = self.factory.makeOCIProject(
pillar=distro, registrant=self.person)
- git_ref = self.factory.makeGitRefs()[0]
+ [git_ref] = self.factory.makeGitRefs(
+ paths=['refs/heads/v1.0-20.04'])
oci_project_url = api_url(oci_project)
git_ref_url = api_url(git_ref)
diff --git a/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py b/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
index 0fb7574..ad4f625 100644
--- a/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
+++ b/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
@@ -208,7 +208,7 @@ class TestAsyncOCIRecipeBuildBehaviour(
@defer.inlineCallbacks
def test_composeBuildRequest(self):
- [ref] = self.factory.makeGitRefs()
+ [ref] = self.factory.makeGitRefs(paths=['refs/heads/v1.0-20.04'])
job = self.makeJob(git_ref=ref)
lfa = self.factory.makeLibraryFileAlias(db_only=True)
job.build.distro_arch_series.addOrUpdateChroot(lfa)
@@ -547,7 +547,8 @@ class TestAsyncOCIRecipeBuildBehaviour(
# If the source Git reference has been deleted, composeBuildRequest
# raises CannotBuild.
repository = self.factory.makeGitRepository()
- [ref] = self.factory.makeGitRefs(repository=repository)
+ [ref] = self.factory.makeGitRefs(
+ repository=repository, paths=['refs/heads/v1.0-20.04'])
owner = self.factory.makePerson(name="oci-owner")
distribution = self.factory.makeDistribution()
diff --git a/lib/lp/oci/tests/test_ociregistryclient.py b/lib/lp/oci/tests/test_ociregistryclient.py
index 5fe9f2f..44241bc 100644
--- a/lib/lp/oci/tests/test_ociregistryclient.py
+++ b/lib/lp/oci/tests/test_ociregistryclient.py
@@ -125,7 +125,7 @@ class TestOCIRegistryClient(OCIConfigHelperMixin, SpyProxyCallsMixin,
# This produces a git ref that does not match the 'valid' OCI branch
# format, so will not get multiple tags. Multiple tags are tested
# explicitly.
- [git_ref] = self.factory.makeGitRefs()
+ [git_ref] = self.factory.makeGitRefs(paths=['refs/heads/v1.0-20.04'])
recipe = self.factory.makeOCIRecipe(git_ref=git_ref)
self.build = self.factory.makeOCIRecipeBuild(recipe=recipe)
self.push_rule = self.factory.makeOCIPushRule(recipe=self.build.recipe)
@@ -748,7 +748,7 @@ class TestOCIRegistryClient(OCIConfigHelperMixin, SpyProxyCallsMixin,
push_rule = self.build.recipe.push_rules[0]
responses.add(
- "GET", "{}/v2/{}/manifests/edge".format(
+ "GET", "{}/v2/{}/manifests/v1.0-20.04_edge".format(
push_rule.registry_url, push_rule.image_name),
status=404)
self.addManifestResponses(push_rule, status_code=201)
@@ -763,7 +763,7 @@ class TestOCIRegistryClient(OCIConfigHelperMixin, SpyProxyCallsMixin,
auth_call, get_manifest_call, send_manifest_call = responses.calls
self.assertEndsWith(
send_manifest_call.request.url,
- "/v2/%s/manifests/edge" % push_rule.image_name)
+ "/v2/%s/manifests/v1.0-20.04_edge" % push_rule.image_name)
self.assertEqual({
"schemaVersion": 2,
"mediaType": "application/"
@@ -834,7 +834,7 @@ class TestOCIRegistryClient(OCIConfigHelperMixin, SpyProxyCallsMixin,
push_rule = self.build.recipe.push_rules[0]
responses.add(
- "GET", "{}/v2/{}/manifests/edge".format(
+ "GET", "{}/v2/{}/manifests/v1.0-20.04_edge".format(
push_rule.registry_url, push_rule.image_name),
json=current_manifest,
status=200)
@@ -849,7 +849,7 @@ class TestOCIRegistryClient(OCIConfigHelperMixin, SpyProxyCallsMixin,
auth_call, get_manifest_call, send_manifest_call = responses.calls
self.assertEndsWith(
send_manifest_call.request.url,
- "/v2/%s/manifests/edge" % push_rule.image_name)
+ "/v2/%s/manifests/v1.0-20.04_edge" % push_rule.image_name)
self.assertEqual({
"schemaVersion": 2,
"mediaType": "application/"
@@ -901,7 +901,7 @@ class TestOCIRegistryClient(OCIConfigHelperMixin, SpyProxyCallsMixin,
push_rule = self.build.recipe.push_rules[0]
responses.add(
- "GET", "{}/v2/{}/manifests/edge".format(
+ "GET", "{}/v2/{}/manifests/v1.0-20.04_edge".format(
push_rule.registry_url, push_rule.image_name),
json=current_manifest,
status=200)
@@ -916,7 +916,7 @@ class TestOCIRegistryClient(OCIConfigHelperMixin, SpyProxyCallsMixin,
auth_call, get_manifest_call, send_manifest_call = responses.calls
self.assertEndsWith(
send_manifest_call.request.url,
- "/v2/%s/manifests/edge" % push_rule.image_name)
+ "/v2/%s/manifests/v1.0-20.04_edge" % push_rule.image_name)
self.assertEqual({
"schemaVersion": 2,
"mediaType": "application/"
@@ -948,7 +948,7 @@ class TestOCIRegistryClient(OCIConfigHelperMixin, SpyProxyCallsMixin,
push_rule = self.build.recipe.push_rules[0]
responses.add(
- "GET", "{}/v2/{}/manifests/edge".format(
+ "GET", "{}/v2/{}/manifests/v1.0-20.04_edge".format(
push_rule.registry_url, push_rule.image_name),
json={"error": "Unknown"},
status=503)