launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #24817
[Merge] ~cjwatson/launchpad:oci-registry-upload-status-ui into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:oci-registry-upload-status-ui into launchpad:master with ~cjwatson/launchpad:oci-recipe-schedule-registry-upload as a prerequisite.
Commit message:
Add UI indicating status of OCI registry uploads
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/384980
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:oci-registry-upload-status-ui into launchpad:master.
diff --git a/lib/lp/oci/browser/ocirecipebuild.py b/lib/lp/oci/browser/ocirecipebuild.py
index 5d58f5e..b38a531 100644
--- a/lib/lp/oci/browser/ocirecipebuild.py
+++ b/lib/lp/oci/browser/ocirecipebuild.py
@@ -20,7 +20,10 @@ from lp.app.browser.launchpadform import (
action,
LaunchpadFormView,
)
-from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuild
+from lp.oci.interfaces.ocirecipebuild import (
+ CannotScheduleRegistryUpload,
+ IOCIRecipeBuild,
+ )
from lp.services.librarian.browser import (
FileNavigationMixin,
ProxiedLibraryFileAlias,
@@ -93,6 +96,18 @@ class OCIRecipeBuildView(LaunchpadFormView):
def next_url(self):
return canonical_url(self.context)
+ @action("Upload build to registries", name="upload")
+ def upload_action(self, action, data):
+ """Schedule an upload of this build to each configured registry."""
+ try:
+ self.context.scheduleRegistryUpload()
+ except CannotScheduleRegistryUpload as e:
+ self.request.response.addWarningNotification(str(e))
+ else:
+ self.request.response.addInfoNotification(
+ "An upload has been scheduled and will run as soon as "
+ "possible.")
+
class OCIRecipeBuildCancelView(LaunchpadFormView):
"""View for cancelling an OCI recipe build."""
diff --git a/lib/lp/oci/browser/tests/test_ocirecipebuild.py b/lib/lp/oci/browser/tests/test_ocirecipebuild.py
index c366055..625594e 100644
--- a/lib/lp/oci/browser/tests/test_ocirecipebuild.py
+++ b/lib/lp/oci/browser/tests/test_ocirecipebuild.py
@@ -22,7 +22,9 @@ from zope.testbrowser.browser import LinkNotFoundError
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.buildmaster.enums import BuildStatus
from lp.oci.interfaces.ocirecipe import OCI_RECIPE_ALLOW_CREATE
+from lp.oci.interfaces.ocirecipebuildjob import IOCIRegistryUploadJobSource
from lp.services.features.testing import FeatureFixture
+from lp.services.job.interfaces.job import JobStatus
from lp.services.webapp import canonical_url
from lp.testing import (
ANONYMOUS,
@@ -107,6 +109,52 @@ class TestOCIRecipeBuildView(BrowserTestCase):
build_view = create_initialized_view(build, "+index")
self.assertEqual([], build_view.files)
+ def test_registry_upload_status_in_progress(self):
+ build = self.factory.makeOCIRecipeBuild(status=BuildStatus.FULLYBUILT)
+ getUtility(IOCIRegistryUploadJobSource).create(build)
+ build_view = create_initialized_view(build, "+index")
+ self.assertThat(build_view(), soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ "registry upload status", "li",
+ attrs={"id": "registry-upload-status"},
+ text=re.compile(r"^\s*Registry upload in progress\s*$"))))
+
+ def test_registry_upload_status_completed(self):
+ build = self.factory.makeOCIRecipeBuild(status=BuildStatus.FULLYBUILT)
+ job = getUtility(IOCIRegistryUploadJobSource).create(build)
+ naked_job = removeSecurityProxy(job)
+ naked_job.job._status = JobStatus.COMPLETED
+ build_view = create_initialized_view(build, "+index")
+ self.assertThat(build_view(), soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ "registry upload status", "li",
+ attrs={"id": "registry-upload-status"},
+ text=re.compile(r"^\s*Registry upload complete\s*$"))))
+
+ def test_registry_upload_status_failed(self):
+ build = self.factory.makeOCIRecipeBuild(status=BuildStatus.FULLYBUILT)
+ job = getUtility(IOCIRegistryUploadJobSource).create(build)
+ naked_job = removeSecurityProxy(job)
+ naked_job.job._status = JobStatus.FAILED
+ naked_job.error_summary = (
+ "Upload of test-digest for test-image failed")
+ build_view = create_initialized_view(build, "+index")
+ self.assertThat(build_view(), soupmatchers.HTMLContains(
+ soupmatchers.Within(
+ soupmatchers.Tag(
+ "registry upload status", "li",
+ attrs={"id": "registry-upload-status"},
+ text=re.compile(
+ r"^\s*Registry upload failed:\s+"
+ r"Upload of test-digest for test-image failed\s*$")),
+ soupmatchers.Tag(
+ "retry button", "input",
+ attrs={
+ "type": "submit",
+ "name": "field.actions.upload",
+ "value": "Retry",
+ }))))
+
class TestOCIRecipeBuildOperations(BrowserTestCase):
diff --git a/lib/lp/oci/templates/ocirecipebuild-index.pt b/lib/lp/oci/templates/ocirecipebuild-index.pt
index 2f88103..bea084b 100644
--- a/lib/lp/oci/templates/ocirecipebuild-index.pt
+++ b/lib/lp/oci/templates/ocirecipebuild-index.pt
@@ -136,6 +136,36 @@
tal:attributes="href context/upload_log_url">uploadlog</a>
(<span tal:replace="file/content/filesize/fmt:bytes" />)
</li>
+ <li id="registry-upload-status"
+ tal:define="job context/last_registry_upload_job"
+ tal:condition="job">
+ <tal:pending
+ condition="context/registry_upload_status/enumvalue:PENDING">
+ Registry upload in progress
+ </tal:pending>
+ <tal:failed-upload
+ condition="context/registry_upload_status/enumvalue:FAILEDTOUPLOAD">
+ Registry upload failed:
+ <span tal:replace="context/registry_upload_error_summary" />
+ <form action="" method="POST">
+ <input type="submit" name="field.actions.upload" value="Retry" />
+ </form>
+ </tal:failed-upload>
+ <tal:uploaded
+ condition="context/registry_upload_status/enumvalue:UPLOADED">
+ Registry upload complete
+ </tal:uploaded>
+ </li>
+ <li id="registry-upload-status"
+ tal:condition="python:
+ context.status.title == 'Successfully built' and
+ context.recipe.can_upload_to_registry and
+ context.last_registry_upload_job is None">
+ <form action="" method="POST">
+ <input type="submit" name="field.actions.upload"
+ value="Upload this build to registries" />
+ </form>
+ </li>
</ul>
<div