← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~twom/launchpad/snap-release-intents into lp:launchpad

 

Tom Wardill has proposed merging lp:~twom/launchpad/snap-release-intents into lp:launchpad.

Commit message:
Use release intents to release snapbuilds to channels after building

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~twom/launchpad/snap-release-intents/+merge/369028

Use the SCA support for 'release_intents' and pass the required parameters to produce a release to a channel after review stage.
Pass `channels` and `only_if_newer` to ensure that the build is only released if it is newer that the current existing build in the specified channels.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~twom/launchpad/snap-release-intents into lp:launchpad.
=== modified file 'lib/lp/snappy/model/snapbuildjob.py'
--- lib/lp/snappy/model/snapbuildjob.py	2018-12-18 18:10:39 +0000
+++ lib/lp/snappy/model/snapbuildjob.py	2019-06-19 09:42:13 +0000
@@ -350,7 +350,6 @@
                     raise ManualReview(
                         "Package held for manual review on the store; "
                         "cannot release it automatically.")
-                client.release(self.snapbuild, self.store_revision)
             self.error_message = None
         except self.retry_error_types:
             raise

=== modified file 'lib/lp/snappy/model/snapstoreclient.py'
--- lib/lp/snappy/model/snapstoreclient.py	2019-06-14 14:46:15 +0000
+++ lib/lp/snappy/model/snapstoreclient.py	2019-06-19 09:42:13 +0000
@@ -266,6 +266,14 @@
             "series": snap.store_series.name,
             "built_at": snapbuild.date_started.isoformat(),
             }
+        channels = removeSecurityProxy(snap.store_channels)
+        if channels:
+            # This will cause a release
+            # The security proxy is useless and breaks JSON serialisation.
+            data.update({
+                "channels": channels,
+                "only_if_newer": True,
+            })
         # XXX cjwatson 2016-05-09: This should add timeline information, but
         # that's currently difficult in jobs.
         try:
@@ -394,41 +402,3 @@
         if channels is None:
             channels = _default_store_channels
         return channels
-
-    @classmethod
-    def _release(cls, snap, revision):
-        """Release a snap revision to specified channels."""
-        release_url = urlappend(
-            config.snappy.store_url, "dev/api/snap-release/")
-        data = {
-            "name": snap.store_name,
-            "revision": revision,
-            # The security proxy is useless and breaks JSON serialisation.
-            "channels": removeSecurityProxy(snap.store_channels),
-            "series": snap.store_series.name,
-            }
-        # XXX cjwatson 2016-06-28: This should add timeline information, but
-        # that's currently difficult in jobs.
-        try:
-            assert snap.store_secrets is not None
-            urlfetch(
-                release_url, method="POST", json=data,
-                auth=MacaroonAuth(
-                    snap.store_secrets["root"],
-                    snap.store_secrets.get("discharge")))
-        except requests.HTTPError as e:
-            if e.response.status_code == 401:
-                if (e.response.headers.get("WWW-Authenticate") ==
-                        "Macaroon needs_refresh=1"):
-                    raise NeedsRefreshResponse()
-            raise cls._makeSnapStoreError(ReleaseFailedResponse, e)
-
-    @classmethod
-    def release(cls, snapbuild, revision):
-        """See `ISnapStoreClient`."""
-        assert config.snappy.store_url is not None
-        snap = snapbuild.snap
-        assert snap.store_name is not None
-        assert snap.store_series is not None
-        assert snap.store_channels
-        cls.refreshIfNecessary(snap, cls._release, snap, revision)

=== modified file 'lib/lp/snappy/tests/test_snapstoreclient.py'
--- lib/lp/snappy/tests/test_snapstoreclient.py	2019-06-14 14:49:25 +0000
+++ lib/lp/snappy/tests/test_snapstoreclient.py	2019-06-19 09:42:13 +0000
@@ -402,6 +402,41 @@
             ]))
 
     @responses.activate
+    def test_upload_with_release_intent(self):
+        snapbuild = self.makeUploadableSnapBuild()
+        snapbuild.snap.store_channels = ['beta', 'edge']
+        transaction.commit()
+        self._addUnscannedUploadResponse()
+        self._addSnapPushResponse()
+        with dbuser(config.ISnapStoreUploadJobSource.dbuser):
+            self.assertEqual(
+                "http://sca.example/dev/api/snaps/1/builds/1/status";,
+                self.client.upload(snapbuild))
+        requests = [call.request for call in responses.calls]
+        self.assertThat(requests, MatchesListwise([
+            RequestMatches(
+                url=Equals("http://updown.example/unscanned-upload/";),
+                method=Equals("POST"),
+                form_data={
+                    "binary": MatchesStructure.byEquality(
+                        name="binary", filename="test-snap.snap",
+                        value="dummy snap content",
+                        type="application/octet-stream",
+                        )}),
+            RequestMatches(
+                url=Equals("http://sca.example/dev/api/snap-push/";),
+                method=Equals("POST"),
+                headers=ContainsDict(
+                    {"Content-Type": Equals("application/json")}),
+                auth=("Macaroon", MacaroonsVerify(self.root_key)),
+                json_data={
+                    "name": "test-snap", "updown_id": 1, "series": "rolling",
+                    "built_at": snapbuild.date_started.isoformat(),
+                    "only_if_newer": True, "channels": ['beta', 'edge'],
+                    }),
+            ]))
+
+    @responses.activate
     def test_upload_no_discharge(self):
         root_key = hashlib.sha256(self.factory.getUniqueString()).hexdigest()
         root_macaroon = Macaroon(key=root_key)
@@ -638,100 +673,3 @@
         self.assertContentEqual([], responses.calls)
         self.assertIsNone(
             getUtility(IMemcacheClient).get(self.channels_memcache_key))
-
-    @responses.activate
-    def test_release(self):
-        snap = self.factory.makeSnap(
-            store_upload=True,
-            store_series=self.factory.makeSnappySeries(name="rolling"),
-            store_name="test-snap",
-            store_secrets=self._make_store_secrets(),
-            store_channels=["stable", "edge"])
-        snapbuild = self.factory.makeSnapBuild(snap=snap)
-        self._addSnapReleaseResponse()
-        self.client.release(snapbuild, 1)
-        self.assertThat(responses.calls[-1].request, RequestMatches(
-            url=Equals("http://sca.example/dev/api/snap-release/";),
-            method=Equals("POST"),
-            headers=ContainsDict({"Content-Type": Equals("application/json")}),
-            auth=("Macaroon", MacaroonsVerify(self.root_key)),
-            json_data={
-                "name": "test-snap", "revision": 1,
-                "channels": ["stable", "edge"], "series": "rolling",
-                }))
-
-    @responses.activate
-    def test_release_no_discharge(self):
-        root_key = hashlib.sha256(self.factory.getUniqueString()).hexdigest()
-        root_macaroon = Macaroon(key=root_key)
-        snap = self.factory.makeSnap(
-            store_upload=True,
-            store_series=self.factory.makeSnappySeries(name="rolling"),
-            store_name="test-snap",
-            store_secrets={"root": root_macaroon.serialize()},
-            store_channels=["stable", "edge"])
-        snapbuild = self.factory.makeSnapBuild(snap=snap)
-        self._addSnapReleaseResponse()
-        self.client.release(snapbuild, 1)
-        self.assertThat(responses.calls[-1].request, RequestMatches(
-            url=Equals("http://sca.example/dev/api/snap-release/";),
-            method=Equals("POST"),
-            headers=ContainsDict({"Content-Type": Equals("application/json")}),
-            auth=("Macaroon", MacaroonsVerify(root_key)),
-            json_data={
-                "name": "test-snap", "revision": 1,
-                "channels": ["stable", "edge"], "series": "rolling",
-                }))
-
-    @responses.activate
-    def test_release_needs_discharge_macaroon_refresh(self):
-        store_secrets = self._make_store_secrets()
-        snap = self.factory.makeSnap(
-            store_upload=True,
-            store_series=self.factory.makeSnappySeries(name="rolling"),
-            store_name="test-snap", store_secrets=store_secrets,
-            store_channels=["stable", "edge"])
-        snapbuild = self.factory.makeSnapBuild(snap=snap)
-        responses.add(
-            "POST", "http://sca.example/dev/api/snap-release/";, status=401,
-            headers={"WWW-Authenticate": "Macaroon needs_refresh=1"})
-        self._addMacaroonRefreshResponse()
-        self._addSnapReleaseResponse()
-        self.client.release(snapbuild, 1)
-        requests = [call.request for call in responses.calls]
-        self.assertThat(requests, MatchesListwise([
-            MatchesStructure.byEquality(path_url="/dev/api/snap-release/"),
-            MatchesStructure.byEquality(path_url="/api/v2/tokens/refresh"),
-            MatchesStructure.byEquality(path_url="/dev/api/snap-release/"),
-            ]))
-        self.assertNotEqual(
-            store_secrets["discharge"], snap.store_secrets["discharge"])
-
-    @responses.activate
-    def test_release_error(self):
-        snap = self.factory.makeSnap(
-            store_upload=True,
-            store_series=self.factory.makeSnappySeries(name="rolling"),
-            store_name="test-snap", store_secrets=self._make_store_secrets(),
-            store_channels=["stable", "edge"])
-        snapbuild = self.factory.makeSnapBuild(snap=snap)
-        responses.add(
-            "POST", "http://sca.example/dev/api/snap-release/";, status=503,
-            json={"error_list": [{"message": "Failed to publish"}]})
-        self.assertRaisesWithContent(
-            ReleaseFailedResponse, "Failed to publish",
-            self.client.release, snapbuild, 1)
-
-    @responses.activate
-    def test_release_404(self):
-        snap = self.factory.makeSnap(
-            store_upload=True,
-            store_series=self.factory.makeSnappySeries(name="rolling"),
-            store_name="test-snap", store_secrets=self._make_store_secrets(),
-            store_channels=["stable", "edge"])
-        snapbuild = self.factory.makeSnapBuild(snap=snap)
-        responses.add(
-            "POST", "http://sca.example/dev/api/snap-release/";, status=404)
-        self.assertRaisesWithContent(
-            ReleaseFailedResponse, b"404 Client Error: Not Found",
-            self.client.release, snapbuild, 1)


Follow ups