← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~ruinedyourlife/launchpad:native-publishing-missing-db-grants into launchpad:db-devel

 

Quentin Debhi has proposed merging ~ruinedyourlife/launchpad:native-publishing-missing-db-grants into launchpad:db-devel.

Commit message:
Add sourcepackagename db grant for native publisher jobs

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~ruinedyourlife/launchpad/+git/launchpad/+merge/486209
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~ruinedyourlife/launchpad:native-publishing-missing-db-grants into launchpad:db-devel.
diff --git a/database/schema/security.cfg b/database/schema/security.cfg
index e812fc4..b56dacd 100644
--- a/database/schema/security.cfg
+++ b/database/schema/security.cfg
@@ -3016,6 +3016,7 @@ public.personsettings                   = SELECT
 public.pocketchroot                     = SELECT
 public.processor                        = SELECT
 public.product                          = SELECT
+public.sourcepackagename                = SELECT
 public.teammembership                   = SELECT
 public.teamparticipation                = SELECT
 public.webhook                          = SELECT
diff --git a/lib/lp/charms/interfaces/charmrecipe.py b/lib/lp/charms/interfaces/charmrecipe.py
index 05a1add..77dcf02 100644
--- a/lib/lp/charms/interfaces/charmrecipe.py
+++ b/lib/lp/charms/interfaces/charmrecipe.py
@@ -74,6 +74,7 @@ from lp.app.errors import NameLookupFailed
 from lp.app.interfaces.informationtype import IInformationType
 from lp.app.validators.name import name_validator
 from lp.app.validators.path import path_does_not_escape
+from lp.buildmaster.builderproxy import FetchServicePolicy
 from lp.code.interfaces.gitref import IGitRef
 from lp.code.interfaces.gitrepository import IGitRepository
 from lp.registry.interfaces.person import IPerson
@@ -783,6 +784,20 @@ class ICharmRecipeAdminAttributes(Interface):
         )
     )
 
+    fetch_service_policy = exported(
+        Choice(
+            title=_("Fetch service policy"),
+            vocabulary=FetchServicePolicy,
+            required=False,
+            readonly=False,
+            default=FetchServicePolicy.STRICT,
+            description=_(
+                "Which policy to use when using the fetch service. Ignored if "
+                "`use_fetch_service` flag is False."
+            ),
+        )
+    )
+
 
 # XXX cjwatson 2021-09-15 bug=760849: "beta" is a lie to get WADL
 # generation working.  Individual attributes must set their version to
@@ -843,6 +858,7 @@ class ICharmRecipeSet(Interface):
         store_channels=None,
         date_created=None,
         use_fetch_service=False,
+        fetch_service_policy=FetchServicePolicy.STRICT,
     ):
         """Create an `ICharmRecipe`."""
 
diff --git a/lib/lp/charms/model/charmrecipe.py b/lib/lp/charms/model/charmrecipe.py
index 04bc448..82ea9c3 100644
--- a/lib/lp/charms/model/charmrecipe.py
+++ b/lib/lp/charms/model/charmrecipe.py
@@ -44,6 +44,7 @@ from lp.app.enums import (
     InformationType,
 )
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
+from lp.buildmaster.builderproxy import FetchServicePolicy
 from lp.buildmaster.enums import BuildStatus
 from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
 from lp.buildmaster.model.builder import Builder
@@ -320,6 +321,13 @@ class CharmRecipe(StormBase, WebhookTargetMixin):
 
     use_fetch_service = Bool(name="use_fetch_service", allow_none=False)
 
+    fetch_service_policy = DBEnum(
+        enum=FetchServicePolicy,
+        default=FetchServicePolicy.STRICT,
+        name="fetch_service_policy",
+        allow_none=True,
+    )
+
     def __init__(
         self,
         registrant,
@@ -339,6 +347,7 @@ class CharmRecipe(StormBase, WebhookTargetMixin):
         store_channels=None,
         date_created=DEFAULT,
         use_fetch_service=False,
+        fetch_service_policy=FetchServicePolicy.STRICT,
     ):
         """Construct a `CharmRecipe`."""
         if not getFeatureFlag(CHARM_RECIPE_ALLOW_CREATE):
@@ -365,6 +374,7 @@ class CharmRecipe(StormBase, WebhookTargetMixin):
         self.store_secrets = store_secrets
         self.store_channels = store_channels
         self.use_fetch_service = use_fetch_service
+        self.fetch_service_policy = fetch_service_policy
 
     def __repr__(self):
         return "<CharmRecipe ~%s/%s/+charm/%s>" % (
@@ -962,6 +972,7 @@ class CharmRecipeSet:
         store_channels=None,
         date_created=DEFAULT,
         use_fetch_service=False,
+        fetch_service_policy=FetchServicePolicy.STRICT,
     ):
         """See `ICharmRecipeSet`."""
         if not registrant.inTeam(owner):
@@ -1008,6 +1019,7 @@ class CharmRecipeSet:
             store_channels=store_channels,
             date_created=date_created,
             use_fetch_service=use_fetch_service,
+            fetch_service_policy=fetch_service_policy,
         )
         store.add(recipe)
 
diff --git a/lib/lp/charms/model/charmrecipebuildbehaviour.py b/lib/lp/charms/model/charmrecipebuildbehaviour.py
index 6e1b0bd..1386da3 100644
--- a/lib/lp/charms/model/charmrecipebuildbehaviour.py
+++ b/lib/lp/charms/model/charmrecipebuildbehaviour.py
@@ -82,6 +82,7 @@ class CharmRecipeBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
         yield self.startProxySession(
             args,
             use_fetch_service=build.recipe.use_fetch_service,
+            fetch_service_policy=build.recipe.fetch_service_policy,
         )
         args["name"] = build.recipe.store_name or build.recipe.name
         channels = build.channels or {}
diff --git a/lib/lp/charms/tests/test_charmrecipe.py b/lib/lp/charms/tests/test_charmrecipe.py
index 0150803..e837e4b 100644
--- a/lib/lp/charms/tests/test_charmrecipe.py
+++ b/lib/lp/charms/tests/test_charmrecipe.py
@@ -38,6 +38,7 @@ from zope.security.proxy import removeSecurityProxy
 
 from lp.app.enums import InformationType
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
+from lp.buildmaster.builderproxy import FetchServicePolicy
 from lp.buildmaster.enums import (
     BuildBaseImageType,
     BuildQueueStatus,
@@ -478,6 +479,9 @@ class TestCharmRecipe(TestCaseWithFactory):
         build_request = self.factory.makeCharmRecipeBuildRequest(recipe=recipe)
         build = recipe.requestBuild(build_request, das)
         self.assertEqual(True, build.recipe.use_fetch_service)
+        self.assertEqual(
+            FetchServicePolicy.STRICT, recipe.fetch_service_policy
+        )
 
     def test_requestBuild_nonvirtualized(self):
         # A non-virtualized processor can build a charm recipe iff the
@@ -1698,6 +1702,9 @@ class TestCharmRecipeSet(TestCaseWithFactory):
         self.assertIsNone(recipe.store_secrets)
         self.assertEqual([], recipe.store_channels)
         self.assertFalse(recipe.use_fetch_service)
+        self.assertEqual(
+            FetchServicePolicy.STRICT, recipe.fetch_service_policy
+        )
 
     def test_creation_no_source(self):
         # Attempting to create a charm recipe without a Git repository
@@ -2149,15 +2156,16 @@ class TestCharmRecipeSet(TestCaseWithFactory):
             git_ref=ref, use_fetch_service=True
         )
 
-        admin_fields = [
-            "require_virtualized",
-            "use_fetch_service",
-        ]
+        admin_fields = {
+            "require_virtualized": True,
+            "use_fetch_service": True,
+            "fetch_service_policy": FetchServicePolicy.PERMISSIVE,
+        }
 
-        for field_name in admin_fields:
+        for field_name, field_value in admin_fields.items():
             # exception is not raised when an admin does the same
             with admin_logged_in():
-                setattr(charm, field_name, True)
+                setattr(charm, field_name, field_value)
 
     def test_non_admins_cannot_update_admin_only_fields(self):
         # The admin fields cannot be updated by a non admin
@@ -2167,12 +2175,13 @@ class TestCharmRecipeSet(TestCaseWithFactory):
             git_ref=ref, use_fetch_service=True
         )
         person = self.factory.makePerson()
-        admin_fields = [
-            "require_virtualized",
-            "use_fetch_service",
-        ]
+        admin_fields = {
+            "require_virtualized": True,
+            "use_fetch_service": True,
+            "fetch_service_policy": FetchServicePolicy.PERMISSIVE,
+        }
 
-        for field_name in admin_fields:
+        for field_name, field_value in admin_fields.items():
             # exception is raised when a non admin updates the fields
             with person_logged_in(person):
                 self.assertRaises(
@@ -2180,7 +2189,7 @@ class TestCharmRecipeSet(TestCaseWithFactory):
                     setattr,
                     charm,
                     field_name,
-                    True,
+                    field_value,
                 )
 
 
diff --git a/lib/lp/charms/tests/test_charmrecipebuildbehaviour.py b/lib/lp/charms/tests/test_charmrecipebuildbehaviour.py
index a2f186a..1d0b134 100644
--- a/lib/lp/charms/tests/test_charmrecipebuildbehaviour.py
+++ b/lib/lp/charms/tests/test_charmrecipebuildbehaviour.py
@@ -36,6 +36,10 @@ from lp.app.enums import InformationType
 from lp.archivepublisher.interfaces.archivegpgsigningkey import (
     IArchiveGPGSigningKey,
 )
+from lp.buildmaster.builderproxy import (
+    FetchServicePolicy,
+    ProxyServiceException,
+)
 from lp.buildmaster.enums import BuildBaseImageType, BuildStatus
 from lp.buildmaster.interactor import shut_down_default_process_pool
 from lp.buildmaster.interfaces.builder import CannotBuild
@@ -712,6 +716,61 @@ class TestAsyncCharmRecipeBuildBehaviourFetchService(
         )
 
     @defer.inlineCallbacks
+    def test_requestFetchServiceSession_permissive(self):
+        """Create a charm recipe build request with a successful fetch service
+        configuration.
+
+        `proxy_url` and `revocation_endpoint` are correctly populated.
+        """
+        job = self.makeJob(
+            use_fetch_service=True,
+            fetch_service_policy=FetchServicePolicy.PERMISSIVE,
+        )
+        args = yield job.extraBuildArgs()
+        request_matcher = MatchesDict(
+            {
+                "method": Equals(b"POST"),
+                "uri": Equals(b"/session"),
+                "headers": ContainsDict(
+                    {
+                        b"Authorization": MatchesListwise(
+                            [
+                                Equals(
+                                    b"Basic "
+                                    + base64.b64encode(
+                                        b"admin-launchpad.test:admin-secret"
+                                    )
+                                )
+                            ]
+                        ),
+                        b"Content-Type": MatchesListwise(
+                            [
+                                Equals(b"application/json"),
+                            ]
+                        ),
+                    }
+                ),
+                "json": MatchesDict(
+                    {
+                        "policy": Equals("permissive"),
+                    }
+                ),
+            }
+        )
+        self.assertThat(
+            self.fetch_service_api.sessions.requests,
+            MatchesListwise([request_matcher]),
+        )
+        self.assertIn("proxy_url", args)
+        self.assertIn("revocation_endpoint", args)
+        self.assertTrue(args["use_fetch_service"])
+        self.assertIn("secrets", args)
+        self.assertIn("fetch_service_mitm_certificate", args["secrets"])
+        self.assertIn(
+            "fake-cert", args["secrets"]["fetch_service_mitm_certificate"]
+        )
+
+    @defer.inlineCallbacks
     def test_requestFetchServiceSession_mitm_certficate_redacted(self):
         """The `fetch_service_mitm_certificate` field in the build arguments
         is redacted in the build logs."""
@@ -806,6 +865,7 @@ class TestAsyncCharmRecipeBuildBehaviourFetchService(
         # The expected file is created in the `tem_upload_path`
         expected_filename = f"{job.build.build_cookie}_metadata.json"
         expected_file_path = os.path.join(tem_upload_path, expected_filename)
+        self.assertTrue(os.path.exists(expected_file_path))
         with open(expected_file_path) as f:
             self.assertEqual(
                 json.dumps(self.fetch_service_api.sessions.responses[1]),
@@ -832,6 +892,22 @@ class TestAsyncCharmRecipeBuildBehaviourFetchService(
         # No calls go through to the fetch service
         self.assertEqual(0, len(self.fetch_service_api.sessions.requests))
 
+    @defer.inlineCallbacks
+    def test_endProxySession_bad_revocation_endpoint(self):
+        """When `revocation_endpoint` is not properly set, it should raise
+        an exception."""
+
+        job = self.makeJob(use_fetch_service=True)
+        job._worker.proxy_info = MagicMock(
+            return_value={
+                "use_fetch_service": True,
+                "revocation_endpoint": "http://bad/endpoint";,
+            }
+        )
+        yield job.extraBuildArgs()
+        with ExpectedException(ProxyServiceException):
+            yield job.endProxySession(upload_path="ignored")
+
 
 class MakeCharmRecipeBuildMixin:
     """Provide the common makeBuild method returning a queued build."""
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index 31bd3ed..1574c4b 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -6752,6 +6752,7 @@ class LaunchpadObjectFactory(ObjectFactory):
         store_channels=None,
         date_created=DEFAULT,
         use_fetch_service=False,
+        fetch_service_policy=FetchServicePolicy.STRICT,
     ):
         """Make a new charm recipe."""
         if registrant is None:
@@ -6801,6 +6802,7 @@ class LaunchpadObjectFactory(ObjectFactory):
             store_channels=store_channels,
             date_created=date_created,
             use_fetch_service=use_fetch_service,
+            fetch_service_policy=fetch_service_policy,
         )
         if is_stale is not None:
             removeSecurityProxy(recipe).is_stale = is_stale