launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #30981
[Merge] ~ines-almeida/launchpad-buildd:update-close-session-for-fetch-service into launchpad-buildd:master
Ines Almeida has proposed merging ~ines-almeida/launchpad-buildd:update-close-session-for-fetch-service into launchpad-buildd:master.
Commit message:
WIP: Update end-of-session code for snaps that use the fetch service
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~ines-almeida/launchpad-buildd/+git/launchpad-buildd/+merge/462875
This is WIP code with some TODOs and open questions to answer.
The goal is to get early feedback and direction.
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~ines-almeida/launchpad-buildd:update-close-session-for-fetch-service into launchpad-buildd:master.
diff --git a/lpbuildd/proxy.py b/lpbuildd/proxy.py
index ea33dda..5b3d576 100644
--- a/lpbuildd/proxy.py
+++ b/lpbuildd/proxy.py
@@ -3,6 +3,7 @@
import base64
import io
+import os
from urllib.parse import urlparse
from twisted.application import strports
@@ -214,8 +215,21 @@ class BuilderProxyFactory(http.HTTPFactory):
class BuildManagerProxyMixin:
+
+ @property
+ def _use_fetch_service(self):
+ return (
+ hasattr(self, 'use_fetch_service')
+ and getattr(self, "use_fetch_service")
+ )
+
def startProxy(self):
"""Start the local builder proxy, if necessary."""
+
+ if self._use_fetch_service:
+ # TODO what to do if it is using the fetch service?
+ return []
+
if not self.proxy_url:
return []
proxy_port = self._builder._config.get("builder", "proxyport")
@@ -232,6 +246,11 @@ class BuildManagerProxyMixin:
def stopProxy(self):
"""Stop the local builder proxy, if necessary."""
+
+ if self._use_fetch_service:
+ # TODO what to do if it is using the fetch service?
+ return
+
if self.proxy_service is None:
return
self.proxy_service.disownServiceParent()
@@ -243,6 +262,21 @@ class BuildManagerProxyMixin:
return
self._builder.log("Revoking proxy token...\n")
try:
- revoke_proxy_token(self.proxy_url, self.revocation_endpoint)
+ response = revoke_proxy_token(
+ self.proxy_url,
+ self.revocation_endpoint,
+ self._use_fetch_service,
+ )
except RevokeProxyTokenError as e:
self._builder.log(f"{e}\n")
+ return
+
+ # When using the fetch service, we want to retrieve and store the
+ # metadata we receive at the end of the session.
+ # This file will be uploaded when we run the `gatherResults` method
+ if self._use_fetch_service:
+ output_path = os.path.join(
+ "/build", self.name, "fetch-service-metadata.json"
+ )
+ with self.backend.open(output_path, mode="wb") as f:
+ f.write(response.content)
diff --git a/lpbuildd/snap.py b/lpbuildd/snap.py
index 02390d4..765bdbf 100644
--- a/lpbuildd/snap.py
+++ b/lpbuildd/snap.py
@@ -38,6 +38,7 @@ class SnapBuildManager(BuildManagerProxyMixin, DebianBuildManager):
self.branch = extra_args.get("branch")
self.git_repository = extra_args.get("git_repository")
self.git_path = extra_args.get("git_path")
+ self.use_fetch_service = extra_args.get("use_fetch_service")
self.proxy_url = extra_args.get("proxy_url")
self.revocation_endpoint = extra_args.get("revocation_endpoint")
self.build_source_tarball = extra_args.get(
@@ -100,6 +101,8 @@ class SnapBuildManager(BuildManagerProxyMixin, DebianBuildManager):
if self.target_architectures:
for arch in self.target_architectures:
args.extend(["--target-arch", arch])
+ if self.use_fetch_service:
+ args.append("--use_fetch_service")
args.append(self.name)
self.runTargetSubProcess("buildsnap", *args)
@@ -141,7 +144,14 @@ class SnapBuildManager(BuildManagerProxyMixin, DebianBuildManager):
# `.comp` files are the binary result of building snap
# components, see spec SD149.
if entry.endswith(
- (".snap", ".manifest", ".debug", ".dpkg.yaml", ".comp")
+ (
+ ".snap",
+ ".manifest",
+ ".debug",
+ ".dpkg.yaml",
+ ".comp",
+ ".json",
+ )
):
self.addWaitingFileFromBackend(path)
if self.build_source_tarball:
diff --git a/lpbuildd/target/build_snap.py b/lpbuildd/target/build_snap.py
index d9052f2..ef74958 100644
--- a/lpbuildd/target/build_snap.py
+++ b/lpbuildd/target/build_snap.py
@@ -102,6 +102,12 @@ class BuildSnap(
action="store_true",
help="disable proxy access after the pull phase has finished",
)
+ parser.add_argument(
+ "--use_fetch_service",
+ default=False,
+ action="store_true",
+ help="use the fetch service instead of the old builder proxy",
+ )
parser.add_argument("name", help="name of snap to build")
def install_svn_servers(self):
@@ -227,9 +233,18 @@ class BuildSnap(
self.args.disable_proxy_after_pull
and self.args.upstream_proxy_url
and self.args.revocation_endpoint
+ and not self.args.use_fetch_service
):
logger.info("Revoking proxy token...")
try:
+ # TODO Should we revoke the proxy token even when we are
+ # using the fetch service here? If there were 2 revocation
+ # endpoints (one for revoking the token, another to end the
+ # session and get the metadata), then we would revoke the token
+ # here, and only end the session later. Given currently, it's
+ # only one endpoint, then we would closing the session and
+ # getting the metadata here, which doesn't seem right.
+ # Note: regardless of the
revoke_proxy_token(
self.args.upstream_proxy_url, self.args.revocation_endpoint
)
diff --git a/lpbuildd/tests/test_snap.py b/lpbuildd/tests/test_snap.py
index 9bef60a..bd5fe12 100644
--- a/lpbuildd/tests/test_snap.py
+++ b/lpbuildd/tests/test_snap.py
@@ -754,6 +754,13 @@ class TestSnapBuildManagerIteration(TestCase):
yield self.startBuild(args, expected_options)
@defer.inlineCallbacks
+ def test_iterate_use_fetch_service(self):
+ # The build manager can be told to use the fetch service as its proxy.
+ args = {"use_fetch_service": True}
+ expected_options = ["--use_fetch_service"]
+ yield self.startBuild(args, expected_options)
+
+ @defer.inlineCallbacks
def test_iterate_disable_proxy_after_pull(self):
self.builder._config.set("builder", "proxyport", "8222")
args = {
@@ -874,3 +881,33 @@ class TestSnapBuildManagerIteration(TestCase):
# XXX cjwatson 2023-02-07: Ideally we'd check the timeout as well,
# but the version of responses in Ubuntu 20.04 doesn't store it
# anywhere we can get at it.
+
+ @responses.activate
+ def test_revokeProxyToken_fetch_service(self):
+ session_id = "123"
+ token = "test_token"
+
+ responses.add(
+ "DELETE", f"http://fetch-service.example/{session_id}"
+ )
+
+ self.buildmanager.backend_name = "fake"
+ self.buildmanager.initiate(
+ {}, "chroot.tar.gz", {"name": "test-snap", "series": "xenial"}
+ )
+ self.buildmanager.use_fetch_service = True
+ self.buildmanager.revocation_endpoint = (
+ f"http://fetch-service.example/{session_id}"
+ )
+ self.buildmanager.proxy_url = f"http://session_id:{token}@proxy.example"
+
+ self.buildmanager.revokeProxyToken()
+
+ self.assertEqual(1, len(responses.calls))
+ request = responses.calls[0].request
+ self.assertEqual(f"Basic {token}", request.headers["Authorization"])
+ self.assertEqual(
+ f"http://fetch-service.example/{session_id}", request.url
+ )
+
+ #TODO add tests about the writting of the metadata file for fetch service
diff --git a/lpbuildd/tests/test_util.py b/lpbuildd/tests/test_util.py
index 005d42b..ea3d8e4 100644
--- a/lpbuildd/tests/test_util.py
+++ b/lpbuildd/tests/test_util.py
@@ -1,9 +1,12 @@
# Copyright 2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
+import base64
+import responses
+
from testtools import TestCase
-from lpbuildd.util import get_arch_bits, set_personality, shell_escape
+from lpbuildd.util import get_arch_bits, revoke_proxy_token, set_personality, shell_escape
class TestShellEscape(TestCase):
@@ -59,3 +62,44 @@ class TestSetPersonality(TestCase):
["linux64", "sbuild"],
set_personality(["sbuild"], "amd64", series="trusty"),
)
+
+
+class TestRevokeToken(TestCase):
+
+ @responses.activate
+ def test_revoke_proxy_token(self):
+ """Proxy token revocation uses the right authentication"""
+
+ proxy_url = "https://username:password@host:port"
+ revocation_endpoint = "https://builder.api.endpoint.token"
+ token = base64.b64encode(b"username:password").decode()
+
+ responses.add(responses.DELETE, revocation_endpoint)
+
+ response = revoke_proxy_token(proxy_url, revocation_endpoint)
+ self.assertEqual(
+ f"Basic {token}", response.request.headers["Authorization"]
+ )
+
+ @responses.activate
+ def test_revoke_fetch_service_token(self):
+ """Proxy token revocation for the fetch service, uses the right
+ authentication and returns metadata """
+
+ proxy_url = f"https://session_id:test-token@host:port"
+ revocation_endpoint = "https://builder.api.endpoint/session_id"
+ response_metadata = {"metadata": "test"}
+
+ responses.add(
+ responses.DELETE, revocation_endpoint, json=response_metadata
+ )
+
+ response = revoke_proxy_token(
+ proxy_url,
+ revocation_endpoint,
+ use_fetch_service=True,
+ )
+ self.assertEqual(
+ f"Basic test-token", response.request.headers["Authorization"]
+ )
+ self.assertEqual(response_metadata, response.json())
diff --git a/lpbuildd/util.py b/lpbuildd/util.py
index eb35f01..e3188b1 100644
--- a/lpbuildd/util.py
+++ b/lpbuildd/util.py
@@ -1,6 +1,7 @@
# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
+import base64
import os
import subprocess
import sys
@@ -68,15 +69,37 @@ class RevokeProxyTokenError(Exception):
return f"Unable to revoke token for {self.username}: {self.exception}"
-def revoke_proxy_token(proxy_url, revocation_endpoint):
+def revoke_proxy_token(proxy_url, revocation_endpoint, use_fetch_service=False):
"""Revoke builder proxy token.
+ The proxy_url for the current Builder Proxy:
+ http://{username}:{password}@{host}:{port}
+
+ We use the username-password combo for authentication.
+
+ The proxy_url for the fetch service:
+ http://{session_id}:{token}@{host}:{port}
+
+ We use the token for authentication.
+
:raises RevokeProxyTokenError: if attempting to revoke the token failed.
"""
url = urlparse(proxy_url)
+
+ # For the fetch service URL case, the {session_id}:{token} will be parsed
+ # as `username` and `password` respectively
+ if use_fetch_service:
+ token = url.password
+ else:
+ token = base64.b64encode(
+ f"{url.username}:{url.password}".encode()
+ ).decode()
+
try:
- requests.delete(
- revocation_endpoint, auth=(url.username, url.password), timeout=15
+ return requests.delete(
+ revocation_endpoint,
+ headers={'Authorization': f'Basic {token}'},
+ timeout=15,
)
except requests.RequestException as e:
raise RevokeProxyTokenError(url.username, e)
Follow ups